diff options
Diffstat (limited to 'lib')
339 files changed, 7105 insertions, 4455 deletions
diff --git a/lib/hiera_puppet.rb b/lib/hiera_puppet.rb index 0e6a572a8..d90b82089 100644 --- a/lib/hiera_puppet.rb +++ b/lib/hiera_puppet.rb @@ -71,12 +71,12 @@ module HieraPuppet if Puppet.settings[:hiera_config].is_a?(String) expanded_config_file = File.expand_path(Puppet.settings[:hiera_config]) - if File.exist?(expanded_config_file) + if Puppet::FileSystem::File.exist?(expanded_config_file) config_file = expanded_config_file end elsif Puppet.settings[:confdir].is_a?(String) expanded_config_file = File.expand_path(File.join(Puppet.settings[:confdir], '/hiera.yaml')) - if File.exist?(expanded_config_file) + if Puppet::FileSystem::File.exist?(expanded_config_file) config_file = expanded_config_file end end diff --git a/lib/puppet/agent.rb b/lib/puppet/agent.rb index 678fdb5d0..14c6c693b 100644 --- a/lib/puppet/agent.rb +++ b/lib/puppet/agent.rb @@ -1,4 +1,3 @@ -require 'sync' require 'puppet/application' # A general class for triggering a run of another @@ -42,7 +41,7 @@ class Puppet::Agent with_client do |client| begin client_args = client_options.merge(:pluginsync => Puppet[:pluginsync]) - sync.synchronize { lock { client.run(client_args) } } + lock { client.run(client_args) } rescue SystemExit,NoMemoryError raise rescue Exception => detail @@ -76,10 +75,6 @@ class Puppet::Agent @splayed = true end - def sync - @sync ||= Sync.new - end - def run_in_fork(forking = true) return yield unless forking or Puppet.features.windows? diff --git a/lib/puppet/application.rb b/lib/puppet/application.rb index 463c77b32..acef32134 100644 --- a/lib/puppet/application.rb +++ b/lib/puppet/application.rb @@ -381,18 +381,31 @@ class Application Puppet::Util::Log.newdestination(:console) end + set_log_level + + Puppet::Util::Log.setup_default unless options[:setdest] + end + + def set_log_level if options[:debug] Puppet::Util::Log.level = :debug elsif options[:verbose] Puppet::Util::Log.level = :info end + end - Puppet::Util::Log.setup_default unless options[:setdest] + def handle_logdest_arg(arg) + begin + Puppet::Util::Log.newdestination(arg) + options[:setdest] = true + rescue => detail + Puppet.log_exception(detail) + end end def configure_indirector_routes route_file = Puppet[:route_file] - if ::File.exists?(route_file) + if Puppet::FileSystem::File.exist?(route_file) routes = YAML.load_file(route_file) application_routes = routes[name.to_s] Puppet::Indirector.configure_routes(application_routes) if application_routes diff --git a/lib/puppet/application/agent.rb b/lib/puppet/application/agent.rb index 3a42dd83e..d86f3f47f 100644 --- a/lib/puppet/application/agent.rb +++ b/lib/puppet/application/agent.rb @@ -67,12 +67,7 @@ class Puppet::Application::Agent < Puppet::Application end option("--logdest DEST", "-l DEST") do |arg| - begin - Puppet::Util::Log.newdestination(arg) - options[:setdest] = true - rescue => detail - Puppet.log_exception(detail) - end + handle_logdest_arg(arg) end option("--waitforcert WAITFORCERT", "-w") do |arg| @@ -442,7 +437,7 @@ Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License def setup_listen(daemon) Puppet.warning "Puppet --listen / kick is deprecated. See http://links.puppetlabs.com/puppet-kick-deprecation" - unless FileTest.exists?(Puppet[:rest_authconfig]) + unless Puppet::FileSystem::File.exist?(Puppet[:rest_authconfig]) Puppet.err "Will not start without authorization file #{Puppet[:rest_authconfig]}" exit(14) end diff --git a/lib/puppet/application/apply.rb b/lib/puppet/application/apply.rb index 3d7370d2c..2eb4415b3 100644 --- a/lib/puppet/application/apply.rb +++ b/lib/puppet/application/apply.rb @@ -19,12 +19,7 @@ class Puppet::Application::Apply < Puppet::Application end option("--logdest LOGDEST", "-l") do |arg| - begin - Puppet::Util::Log.newdestination(arg) - options[:logset] = true - rescue => detail - $stderr.puts detail.to_s - end + handle_logdest_arg(arg) end option("--parseonly") do |args| @@ -168,7 +163,7 @@ Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License Puppet[:code] = options[:code] || STDIN.read else manifest = command_line.args.shift - raise "Could not find file #{manifest}" unless ::File.exist?(manifest) + raise "Could not find file #{manifest}" unless Puppet::FileSystem::File.exist?(manifest) Puppet.warning("Only one file can be applied per run. Skipping #{command_line.args.join(', ')}") if command_line.args.size > 0 Puppet[:manifest] = manifest end @@ -194,7 +189,7 @@ Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License # Allow users to load the classes that puppet agent creates. if options[:loadclasses] file = Puppet[:classfile] - if FileTest.exists?(file) + if Puppet::FileSystem::File.exist?(file) unless FileTest.readable?(file) $stderr.puts "#{file} is not readable" exit(63) @@ -238,7 +233,7 @@ Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License def setup exit(Puppet.settings.print_configs ? 0 : 1) if Puppet.settings.print_configs? - Puppet::Util::Log.newdestination(:console) unless options[:logset] + Puppet::Util::Log.newdestination(:console) unless options[:setdest] Signal.trap(:INT) do $stderr.puts "Exiting" @@ -248,10 +243,10 @@ Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License # we want the last report to be persisted locally Puppet::Transaction::Report.indirection.cache_class = :yaml - if options[:debug] - Puppet::Util::Log.level = :debug - elsif options[:verbose] - Puppet::Util::Log.level = :info + set_log_level + + if Puppet[:profile] + Puppet::Util::Profiler.current = Puppet::Util::Profiler::WallClock.new(Puppet.method(:debug), "apply") end end diff --git a/lib/puppet/application/cert.rb b/lib/puppet/application/cert.rb index edf15f3b9..6ad901593 100644 --- a/lib/puppet/application/cert.rb +++ b/lib/puppet/application/cert.rb @@ -1,4 +1,5 @@ require 'puppet/application' +require 'puppet/ssl/certificate_authority/interface' class Puppet::Application::Cert < Puppet::Application @@ -36,11 +37,36 @@ class Puppet::Application::Cert < Puppet::Application Puppet::Util::Log.level = :debug end - require 'puppet/ssl/certificate_authority/interface' - Puppet::SSL::CertificateAuthority::Interface::INTERFACE_METHODS.reject {|m| m == :destroy }.each do |method| - option("--#{method.to_s.gsub('_','-')}", "-#{method.to_s[0,1]}") do |arg| - self.subcommand = method - end + option("--list", "-l") do |arg| + self.subcommand = :list + end + + option("--revoke", "-r") do |arg| + self.subcommand = :revoke + end + + option("--generate", "-g") do |arg| + self.subcommand = :generate + end + + option("--sign", "-s") do |arg| + self.subcommand = :sign + end + + option("--print", "-p") do |arg| + self.subcommand = :print + end + + option("--verify", "-v") do |arg| + self.subcommand = :verify + end + + option("--fingerprint", "-f") do |arg| + self.subcommand = :fingerprint + end + + option("--reinventory") do |arg| + self.subcommand = :reinventory end option("--[no-]allow-dns-alt-names") do |value| @@ -120,6 +146,11 @@ unless the '--all' option is set. * verify: Verify the named certificate against the local CA certificate. +* reinventory: + Build an inventory of the issued certificates. This will destroy the current + inventory file specified by 'cert_inventory' and recreate it from the + certificates found in the 'certdir'. Ensure the puppet master is stopped + before running this action. OPTIONS ------- @@ -184,8 +215,8 @@ Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License hosts = command_line.args.collect { |h| h.downcase } end begin - @ca.apply(:revoke, options.merge(:to => hosts)) if subcommand == :destroy - @ca.apply(subcommand, options.merge(:to => hosts, :digest => @digest)) + apply(@ca, :revoke, options.merge(:to => hosts)) if subcommand == :destroy + apply(@ca, subcommand, options.merge(:to => hosts, :digest => @digest)) rescue => detail Puppet.log_exception(detail) exit(24) @@ -234,4 +265,13 @@ Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License end result end + + # Create and run an applicator. I wanted to build an interface where you could do + # something like 'ca.apply(:generate).to(:all) but I don't think it's really possible. + def apply(ca, method, options) + raise ArgumentError, "You must specify the hosts to apply to; valid values are an array or the symbol :all" unless options[:to] + applier = Puppet::SSL::CertificateAuthority::Interface.new(method, options) + applier.apply(ca) + end + end diff --git a/lib/puppet/application/device.rb b/lib/puppet/application/device.rb index b80691f3c..5eef49fef 100644 --- a/lib/puppet/application/device.rb +++ b/lib/puppet/application/device.rb @@ -47,12 +47,7 @@ class Puppet::Application::Device < Puppet::Application end option("--logdest DEST", "-l DEST") do |arg| - begin - Puppet::Util::Log.newdestination(arg) - options[:setdest] = true - rescue => detail - Puppet.log_exception(detail) - end + handle_logdest_arg(arg) end option("--waitforcert WAITFORCERT", "-w") do |arg| diff --git a/lib/puppet/application/face_base.rb b/lib/puppet/application/face_base.rb index 86cc6a0d8..3154d6de4 100644 --- a/lib/puppet/application/face_base.rb +++ b/lib/puppet/application/face_base.rb @@ -240,7 +240,7 @@ class Puppet::Application::FaceBase < Puppet::Application rescue SystemExit => detail status = detail.status - rescue Exception => detail + rescue => detail Puppet.log_exception(detail) Puppet.err "Try 'puppet help #{@face.name} #{@action.name}' for usage" diff --git a/lib/puppet/application/filebucket.rb b/lib/puppet/application/filebucket.rb index 389e6e4ca..509885602 100644 --- a/lib/puppet/application/filebucket.rb +++ b/lib/puppet/application/filebucket.rb @@ -129,7 +129,7 @@ Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License raise "You must specify a file to back up" unless args.length > 0 args.each do |file| - unless FileTest.exists?(file) + unless Puppet::FileSystem::File.exist?(file) $stderr.puts "#{file}: no such file" next end diff --git a/lib/puppet/application/inspect.rb b/lib/puppet/application/inspect.rb index 3ebeb7875..3aec9fa23 100644 --- a/lib/puppet/application/inspect.rb +++ b/lib/puppet/application/inspect.rb @@ -8,12 +8,7 @@ class Puppet::Application::Inspect < Puppet::Application option("--verbose","-v") option("--logdest LOGDEST", "-l") do |arg| - begin - Puppet::Util::Log.newdestination(arg) - options[:logset] = true - rescue => detail - $stderr.puts detail.to_s - end + handle_logdest_arg(arg) end def help @@ -86,18 +81,14 @@ Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License @report = Puppet::Transaction::Report.new("inspect") Puppet::Util::Log.newdestination(@report) - Puppet::Util::Log.newdestination(:console) unless options[:logset] + Puppet::Util::Log.newdestination(:console) unless options[:setdest] Signal.trap(:INT) do $stderr.puts "Exiting" exit(1) end - if options[:debug] - Puppet::Util::Log.level = :debug - elsif options[:verbose] - Puppet::Util::Log.level = :info - end + set_log_level Puppet::Transaction::Report.indirection.terminus_class = :rest Puppet::Resource::Catalog.indirection.terminus_class = :yaml diff --git a/lib/puppet/application/master.rb b/lib/puppet/application/master.rb index fafbcf3b4..b8f27994f 100644 --- a/lib/puppet/application/master.rb +++ b/lib/puppet/application/master.rb @@ -17,12 +17,7 @@ class Puppet::Application::Master < Puppet::Application end option("--logdest DEST", "-l DEST") do |arg| - begin - Puppet::Util::Log.newdestination(arg) - options[:setdest] = true - rescue => detail - Puppet.log_exception(detail) - end + handle_logdest_arg(arg) end option("--parseonly") do |args| diff --git a/lib/puppet/application/queue.rb b/lib/puppet/application/queue.rb index 87e269e33..e3820e621 100644 --- a/lib/puppet/application/queue.rb +++ b/lib/puppet/application/queue.rb @@ -112,12 +112,7 @@ Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License end option("--logdest DEST", "-l DEST") do |arg| - begin - Puppet::Util::Log.newdestination(arg) - options[:setdest] = true - rescue => detail - Puppet.log_exception(detail) - end + handle_logdest_arg(arg) end def main diff --git a/lib/puppet/application/resource.rb b/lib/puppet/application/resource.rb index d4944f860..35f9f155b 100644 --- a/lib/puppet/application/resource.rb +++ b/lib/puppet/application/resource.rb @@ -14,6 +14,7 @@ class Puppet::Application::Resource < Puppet::Application option("--edit","-e") option("--host HOST","-H") do |arg| + Puppet.warning("Accessing resources on the network is deprecated. See http://links.puppetlabs.com/deprecate-networked-resource") @host = arg end @@ -151,12 +152,7 @@ Copyright (c) 2011 Puppet Labs, LLC Licensed under the Apache 2.0 License def setup Puppet::Util::Log.newdestination(:console) - - if options[:debug] - Puppet::Util::Log.level = :debug - elsif options[:verbose] - Puppet::Util::Log.level = :info - end + set_log_level end private diff --git a/lib/puppet/coercion.rb b/lib/puppet/coercion.rb index f3db8f62b..5de06a363 100644 --- a/lib/puppet/coercion.rb +++ b/lib/puppet/coercion.rb @@ -26,4 +26,15 @@ module Puppet::Coercion fail('expected a boolean value') end end + + # Return the list of acceptable boolean values. + # + # This is limited to lower-case, even though boolean() is case-insensitive. + # + # @return [Array] + # @raise + # @api private + def self.boolean_values + ['true', 'false', 'yes', 'no'] + end end diff --git a/lib/puppet/configurer.rb b/lib/puppet/configurer.rb index 6547f7458..0c000c9ea 100644 --- a/lib/puppet/configurer.rb +++ b/lib/puppet/configurer.rb @@ -44,7 +44,7 @@ class Puppet::Configurer rescue => detail Puppet.log_exception(detail, "Removing corrupt state file #{Puppet[:statefile]}: #{detail}") begin - ::File.unlink(Puppet[:statefile]) + Puppet::FileSystem::File.unlink(Puppet[:statefile]) retry rescue => detail raise Puppet::Error.new("Cannot remove #{Puppet[:statefile]}: #{detail}") @@ -157,7 +157,9 @@ class Puppet::Configurer query_options = nil end end - rescue Puppet::Error, Net::HTTPError => detail + rescue SystemExit,NoMemoryError + raise + rescue Exception => detail Puppet.warning("Unable to fetch my node definition, but the agent run will continue:") Puppet.warning(detail) end @@ -199,7 +201,7 @@ class Puppet::Configurer # Between Puppet runs we need to forget the cached values. This lets us # pick up on new functions installed by gems or new modules being added # without the daemon being restarted. - Thread.current[:env_module_directories] = nil + $env_module_directories = nil Puppet::Util::Log.close(report) send_report(report) diff --git a/lib/puppet/configurer/downloader.rb b/lib/puppet/configurer/downloader.rb index f9aa161ca..56de33608 100644 --- a/lib/puppet/configurer/downloader.rb +++ b/lib/puppet/configurer/downloader.rb @@ -58,7 +58,9 @@ class Puppet::Configurer::Downloader :backup => false, :noop => false }.merge( - Puppet.features.microsoft_windows? ? {} : + Puppet.features.microsoft_windows? ? { + :source_permissions => :ignore + } : { :owner => Process.uid, :group => Process.gid diff --git a/lib/puppet/configurer/plugin_handler.rb b/lib/puppet/configurer/plugin_handler.rb index f0a93eef4..87a307b70 100644 --- a/lib/puppet/configurer/plugin_handler.rb +++ b/lib/puppet/configurer/plugin_handler.rb @@ -11,6 +11,16 @@ module Puppet::Configurer::PluginHandler Puppet[:pluginsignore], @environment ) + if Puppet.features.external_facts? + plugin_fact_downloader = Puppet::Configurer::Downloader.new( + "pluginfacts", + Puppet[:pluginfactdest], + Puppet[:pluginfactsource], + Puppet[:pluginsignore], + @environment + ) + plugin_fact_downloader.evaluate + end plugin_downloader.evaluate Puppet::Util::Autoload.reload_changed diff --git a/lib/puppet/confine.rb b/lib/puppet/confine.rb new file mode 100644 index 000000000..41b5aa0e6 --- /dev/null +++ b/lib/puppet/confine.rb @@ -0,0 +1,80 @@ +# The class that handles testing whether our providers +# actually work or not. +require 'puppet/util' + +class Puppet::Confine + include Puppet::Util + + @tests = {} + + class << self + attr_accessor :name + end + + def self.inherited(klass) + name = klass.to_s.split("::").pop.downcase.to_sym + raise "Test #{name} is already defined" if @tests.include?(name) + + klass.name = name + + @tests[name] = klass + end + + def self.test(name) + unless @tests[name] + begin + require "puppet/confine/#{name}" + rescue LoadError => detail + unless detail.to_s =~ /No such file|cannot load such file/i + warn "Could not load confine test '#{name}': #{detail}" + end + # Could not find file + end + end + @tests[name] + end + + attr_reader :values + + # Mark that this confine is used for testing binary existence. + attr_accessor :for_binary + def for_binary? + for_binary + end + + # Used for logging. + attr_accessor :label + + def initialize(values) + values = [values] unless values.is_a?(Array) + @values = values + end + + # Provide a hook for the message when there's a failure. + def message(value) + "" + end + + # Collect the results of all of them. + def result + values.collect { |value| pass?(value) } + end + + # Test whether our confine matches. + def valid? + values.each do |value| + unless pass?(value) + Puppet.debug(label + ": " + message(value)) + return false + end + end + + return true + ensure + reset + end + + # Provide a hook for subclasses. + def reset + end +end diff --git a/lib/puppet/provider/confine/exists.rb b/lib/puppet/confine/exists.rb index 09f94dfd9..95a315514 100644 --- a/lib/puppet/provider/confine/exists.rb +++ b/lib/puppet/confine/exists.rb @@ -1,12 +1,12 @@ -require 'puppet/provider/confine' +require 'puppet/confine' -class Puppet::Provider::Confine::Exists < Puppet::Provider::Confine +class Puppet::Confine::Exists < Puppet::Confine def self.summarize(confines) confines.inject([]) { |total, confine| total + confine.summary } end def pass?(value) - value && (for_binary? ? which(value) : FileTest.exist?(value)) + value && (for_binary? ? which(value) : Puppet::FileSystem::File.exist?(value)) end def message(value) diff --git a/lib/puppet/provider/confine/false.rb b/lib/puppet/confine/false.rb index 1c11dd40f..3b664df3b 100644 --- a/lib/puppet/provider/confine/false.rb +++ b/lib/puppet/confine/false.rb @@ -1,6 +1,6 @@ -require 'puppet/provider/confine' +require 'puppet/confine' -class Puppet::Provider::Confine::False < Puppet::Provider::Confine +class Puppet::Confine::False < Puppet::Confine def self.summarize(confines) confines.inject(0) { |count, confine| count + confine.summary } end diff --git a/lib/puppet/provider/confine/feature.rb b/lib/puppet/confine/feature.rb index b223b8b11..5aff48a90 100644 --- a/lib/puppet/provider/confine/feature.rb +++ b/lib/puppet/confine/feature.rb @@ -1,6 +1,6 @@ -require 'puppet/provider/confine' +require 'puppet/confine' -class Puppet::Provider::Confine::Feature < Puppet::Provider::Confine +class Puppet::Confine::Feature < Puppet::Confine def self.summarize(confines) confines.collect { |c| c.values }.flatten.uniq.find_all { |value| ! confines[0].pass?(value) } end diff --git a/lib/puppet/provider/confine/true.rb b/lib/puppet/confine/true.rb index 559f2675f..b7e347045 100644 --- a/lib/puppet/provider/confine/true.rb +++ b/lib/puppet/confine/true.rb @@ -1,6 +1,6 @@ -require 'puppet/provider/confine' +require 'puppet/confine' -class Puppet::Provider::Confine::True < Puppet::Provider::Confine +class Puppet::Confine::True < Puppet::Confine def self.summarize(confines) confines.inject(0) { |count, confine| count + confine.summary } end diff --git a/lib/puppet/provider/confine/variable.rb b/lib/puppet/confine/variable.rb index af8e5d314..9102d4577 100644 --- a/lib/puppet/provider/confine/variable.rb +++ b/lib/puppet/confine/variable.rb @@ -1,10 +1,10 @@ -require 'puppet/provider/confine' +require 'puppet/confine' # Require a specific value for a variable, either a Puppet setting # or a Facter value. This class is a bit weird because the name # is set explicitly by the ConfineCollection class -- from this class, # it's not obvious how the name would ever get set. -class Puppet::Provider::Confine::Variable < Puppet::Provider::Confine +class Puppet::Confine::Variable < Puppet::Confine # Provide a hash summary of failing confines -- the key of the hash # is the name of the confine, and the value is the missing yet required values. # Only returns failed values, not all required values. diff --git a/lib/puppet/provider/confine_collection.rb b/lib/puppet/confine_collection.rb index 46fd3baaf..7a2eba8b4 100644 --- a/lib/puppet/provider/confine_collection.rb +++ b/lib/puppet/confine_collection.rb @@ -1,8 +1,8 @@ # Manage a collection of confines, returning a boolean or # helpful information. -require 'puppet/provider/confine' +require 'puppet/confine' -class Puppet::Provider::ConfineCollection +class Puppet::ConfineCollection def confine(hash) if hash.include?(:for_binary) for_binary = true @@ -11,11 +11,11 @@ class Puppet::Provider::ConfineCollection for_binary = false end hash.each do |test, values| - if klass = Puppet::Provider::Confine.test(test) + if klass = Puppet::Confine.test(test) @confines << klass.new(values) @confines[-1].for_binary = true if for_binary else - confine = Puppet::Provider::Confine.test(:variable).new(values) + confine = Puppet::Confine.test(:variable).new(values) confine.name = test @confines << confine end diff --git a/lib/puppet/provider/confiner.rb b/lib/puppet/confiner.rb index a1a2fa593..57176e799 100644 --- a/lib/puppet/provider/confiner.rb +++ b/lib/puppet/confiner.rb @@ -1,10 +1,10 @@ -require 'puppet/provider/confine_collection' +require 'puppet/confine_collection' # The Confiner module contains methods for managing a Provider's confinement (suitability under given # conditions). The intent is to include this module in an object where confinement management is wanted. # It lazily adds an instance variable `@confine_collection` to the object where it is included. # -module Puppet::Provider::Confiner +module Puppet::Confiner # Confines a provider to be suitable only under the given conditions. # The hash describes a confine using mapping from symbols to values or predicate code. # @@ -26,11 +26,11 @@ module Puppet::Provider::Confiner confine_collection.confine(hash) end - # @return [Puppet::Provider::ConfineCollection] the collection of confines + # @return [Puppet::ConfineCollection] the collection of confines # @api private # def confine_collection - @confine_collection ||= Puppet::Provider::ConfineCollection.new(self.to_s) + @confine_collection ||= Puppet::ConfineCollection.new(self.to_s) end # Checks whether this implementation is suitable for the current platform (or returns a summary diff --git a/lib/puppet/daemon.rb b/lib/puppet/daemon.rb index 562602418..c931184f1 100755..100644 --- a/lib/puppet/daemon.rb +++ b/lib/puppet/daemon.rb @@ -149,16 +149,12 @@ class Puppet::Daemon # Create a pidfile for our daemon, so we can be stopped and others # don't try to start. def create_pidfile - Puppet::Util.synchronize_on(Puppet.run_mode.name,Sync::EX) do - raise "Could not create PID file: #{@pidfile.file_path}" unless @pidfile.lock - end + raise "Could not create PID file: #{@pidfile.file_path}" unless @pidfile.lock end # Remove the pid file for our daemon. def remove_pidfile - Puppet::Util.synchronize_on(Puppet.run_mode.name,Sync::EX) do - @pidfile.unlock - end + @pidfile.unlock end def run_event_loop diff --git a/lib/puppet/data_binding.rb b/lib/puppet/data_binding.rb index ce77a9bf8..76cc7a6f3 100644 --- a/lib/puppet/data_binding.rb +++ b/lib/puppet/data_binding.rb @@ -2,39 +2,11 @@ require 'puppet/indirector' # A class for managing data lookups class Puppet::DataBinding + class LookupError < Puppet::Error; end - # Set up indirection, so that data can be looked for in the complier + # Set up indirection, so that data can be looked for in the compiler extend Puppet::Indirector indirects(:data_binding, :terminus_setting => :data_binding_terminus, :doc => "Where to find external data bindings.") - - # A class that acts just enough like a Puppet::Parser::Scope to - # fool Hiera's puppet backend. This class doesn't actually do anything - # but it does allow people to use the puppet backend with the hiera - # data bindings withough causing problems. - class Variables - FAKE_RESOURCE = Struct.new(:name).new("fake").freeze - FAKE_CATALOG = Struct.new(:classes).new([].freeze).freeze - - def initialize(variable_bindings) - @variable_bindings = variable_bindings - end - - def [](name) - @variable_bindings[name] - end - - def resource - FAKE_RESOURCE - end - - def catalog - FAKE_CATALOG - end - - def function_include(name) - # noop - end - end end diff --git a/lib/puppet/defaults.rb b/lib/puppet/defaults.rb index 7a161fc57..9b9502f06 100644 --- a/lib/puppet/defaults.rb +++ b/lib/puppet/defaults.rb @@ -20,23 +20,27 @@ module Puppet :confdir => { :default => nil, :type => :directory, - :desc => - "The main Puppet configuration directory. The default for this setting is calculated based on the user. If the process\n" + - "is running as root or the user that Puppet is supposed to run as, it defaults to a system directory, but if it's running as any other user,\n" + - "it defaults to being in the user's home directory.", + :desc => "The main Puppet configuration directory. The default for this setting + is calculated based on the user. If the process is running as root or + the user that Puppet is supposed to run as, it defaults to a system + directory, but if it's running as any other user, it defaults to being + in the user's home directory.", }, :vardir => { :default => nil, :type => :directory, - :desc => "Where Puppet stores dynamic and growing data. The default for this setting is calculated specially, like `confdir`_.", + :owner => "service", + :group => "service", + :desc => "Where Puppet stores dynamic and growing data. The default for this + setting is calculated specially, like `confdir`_.", }, ### NOTE: this setting is usually being set to a symbol value. We don't officially have a ### setting type for that yet, but we might want to consider creating one. :name => { :default => nil, - :desc => "The name of the application, if we are running as one. The\n" + - "default is essentially $0 without the path or `.rb`.", + :desc => "The name of the application, if we are running as one. The + default is essentially $0 without the path or `.rb`.", } ) @@ -52,6 +56,15 @@ module Puppet ) define_settings(:main, + :priority => { + :default => nil, + :type => :priority, + :desc => "The scheduling priority of the process. Valid values are 'high', + 'normal', 'low', or 'idle', which are mapped to platform-specific + values. The priority can also be specified as an integer value and + will be passed as is, e.g. -5. Puppet must be running as a privileged + user in order to increase scheduling priority.", + }, :trace => { :default => false, :type => :boolean, @@ -70,9 +83,9 @@ module Puppet }, :syslogfacility => { :default => "daemon", - :desc => "What syslog facility to use when logging to\n" + - "syslog. Syslog has a fixed list of valid facilities, and you must\n" + - "choose one of those; you cannot just make one up." + :desc => "What syslog facility to use when logging to syslog. + Syslog has a fixed list of valid facilities, and you must + choose one of those; you cannot just make one up." }, :statedir => { :default => "$vardir/state", @@ -93,30 +106,33 @@ module Puppet :genconfig => { :default => false, :type => :boolean, - :desc => "Whether to just print a configuration to stdout and exit. Only makes\n" + - "sense when used interactively. Takes into account arguments specified\n" + - "on the CLI.", + :desc => "When true, causes Puppet applications to print an example config file + to stdout and exit. The example will include descriptions of each + setting, and the current (or default) value of each setting, + incorporating any settings overridden on the CLI (with the exception + of `genconfig` itself). This setting only makes sense when specified + on the command line as `--genconfig`.", }, :genmanifest => { :default => false, :type => :boolean, - :desc => "Whether to just print a manifest to stdout and exit. Only makes\n" + - "sense when used interactively. Takes into account arguments specified\n" + - "on the CLI.", + :desc => "Whether to just print a manifest to stdout and exit. Only makes + sense when specified on the command line as `--genmanifest`. Takes into account arguments specified + on the CLI.", }, :configprint => { :default => "", - :desc => "Print the value of a specific configuration setting. If the name of a\n" + - "setting is provided for this, then the value is printed and puppet\n" + - "exits. Comma-separate multiple values. For a list of all values,\n" + - "specify 'all'.", + :desc => "Print the value of a specific configuration setting. If the name of a + setting is provided for this, then the value is printed and puppet + exits. Comma-separate multiple values. For a list of all values, + specify 'all'.", }, :color => { :default => "ansi", :type => :string, - :desc => "Whether to use colors when logging to the console. Valid values are\n" + - "`ansi` (equivalent to `true`), `html`, and `false`, which produces no color.\n" + - "Defaults to false on Windows, as its console does not support ansi colors.", + :desc => "Whether to use colors when logging to the console. Valid values are + `ansi` (equivalent to `true`), `html`, and `false`, which produces no color. + Defaults to false on Windows, as its console does not support ansi colors.", }, :mkusers => { :default => false, @@ -131,14 +147,15 @@ module Puppet :onetime => { :default => false, :type => :boolean, - :desc => "Run the configuration once, rather than as a long-running\n" + - "daemon. This is useful for interactively running puppetd.", + :desc => "Perform one configuration run and exit, rather than spawning a long-running + daemon. This is useful for interactively running puppet agent, or + running puppet agent from cron.", :short => 'o', }, :path => { :default => "none", - :desc => "The shell search path. Defaults to whatever is inherited\n" + - "from the parent process.", + :desc => "The shell search path. Defaults to whatever is inherited + from the parent process.", :call_hook => :on_define_and_write, :hook => proc do |value| ENV["PATH"] = "" if ENV["PATH"].nil? @@ -153,11 +170,11 @@ module Puppet :libdir => { :type => :directory, :default => "$vardir/lib", - :desc => "An extra search path for Puppet. This is only useful\n" + - "for those files that Puppet will load on demand, and is only\n" + - "guaranteed to work for those cases. In fact, the autoload\n" + - "mechanism is responsible for making sure this directory\n" + - "is in Ruby's search path\n", + :desc => "An extra search path for Puppet. This is only useful + for those files that Puppet will load on demand, and is only + guaranteed to work for those cases. In fact, the autoload + mechanism is responsible for making sure this directory + is in Ruby's search path\n", :call_hook => :on_initialize_and_write, :hook => proc do |value| $LOAD_PATH.delete(@oldlibdir) if defined?(@oldlibdir) and $LOAD_PATH.include?(@oldlibdir) @@ -168,41 +185,44 @@ module Puppet :ignoreimport => { :default => false, :type => :boolean, - :desc => "If true, allows the parser to continue without requiring\n" + - "all files referenced with `import` statements to exist. This setting was primarily\n" + - "designed for use with commit hooks for parse-checking.", + :desc => "If true, allows the parser to continue without requiring + all files referenced with `import` statements to exist. This setting was primarily + designed for use with commit hooks for parse-checking.", }, :environment => { :default => "production", - :desc => "The environment Puppet is running in. For clients\n" + - "(e.g., `puppet agent`) this determines the environment itself, which\n" + - "is used to find modules and much more. For servers (i.e., `puppet master`)\n" + - "this provides the default environment for nodes we know nothing about." + :desc => "The environment Puppet is running in. For clients + (e.g., `puppet agent`) this determines the environment itself, which + is used to find modules and much more. For servers (i.e., `puppet master`) + this provides the default environment for nodes we know nothing about." }, :diff_args => { :default => default_diffargs, - :desc => "Which arguments to pass to the diff command when printing differences between\n" + - "files. The command to use can be chosen with the `diff` setting.", + :desc => "Which arguments to pass to the diff command when printing differences between + files. The command to use can be chosen with the `diff` setting.", }, :diff => { :default => (Puppet.features.microsoft_windows? ? "" : "diff"), - :desc => "Which diff command to use when printing differences between files. This setting\n" + - "has no default value on Windows, as standard `diff` is not available, but Puppet can use many\n" + - "third-party diff tools.", + :desc => "Which diff command to use when printing differences between files. This setting + has no default value on Windows, as standard `diff` is not available, but Puppet can use many + third-party diff tools.", }, :show_diff => { :type => :boolean, :default => false, - :desc => "Whether to log and report a contextual diff when files are being replaced. This causes\n" + - "partial file contents to pass through Puppet's normal logging and reporting system, so this setting\n" + - "should be used with caution if you are sending Puppet's reports to an insecure destination.\n" + - "This feature currently requires the `diff/lcs` Ruby library.", + :desc => "Whether to log and report a contextual diff when files are being replaced. + This causes partial file contents to pass through Puppet's normal + logging and reporting system, so this setting should be used with + caution if you are sending Puppet's reports to an insecure + destination. This feature currently requires the `diff/lcs` Ruby + library.", }, :daemonize => { :type => :boolean, :default => (Puppet.features.microsoft_windows? ? false : true), - :desc => "Whether to send the process into the background. This defaults to true on POSIX systems, - and to false on Windows (where Puppet currently cannot daemonize).", + :desc => "Whether to send the process into the background. This defaults + to true on POSIX systems, and to false on Windows (where Puppet + currently cannot daemonize).", :short => "D", :hook => proc do |value| if value and Puppet.features.microsoft_windows? @@ -212,10 +232,11 @@ module Puppet }, :maximum_uid => { :default => 4294967290, - :desc => "The maximum allowed UID. Some platforms use negative UIDs\n" + - "but then ship with tools that do not know how to handle signed ints, so the UIDs show up as\n" + - "huge numbers that can then not be fed back into the system. This is a hackish way to fail in a\n" + - "slightly more useful way when that happens.", + :desc => "The maximum allowed UID. Some platforms use negative UIDs + but then ship with tools that do not know how to handle signed ints, + so the UIDs show up as huge numbers that can then not be fed back into + the system. This is a hackish way to fail in a slightly more useful + way when that happens.", }, :route_file => { :default => "$confdir/routes.yaml", @@ -385,14 +406,21 @@ module Puppet :freeze_main => { :default => false, :type => :boolean, - :desc => "Freezes the 'main' class, disallowing any code to be added to it. This\n" + - "essentially means that you can't have any code outside of a node, class, or definition other\n" + - "than in the site manifest.", + :desc => "Freezes the 'main' class, disallowing any code to be added to it. This + essentially means that you can't have any code outside of a node, + class, or definition other than in the site manifest.", }, :stringify_facts => { :default => true, :type => :boolean, - :desc => "Flatten fact values to strings using #to_s. Means you can't have arrays or hashes as fact values.", + :desc => "Flatten fact values to strings using #to_s. Means you can't have arrays or + hashes as fact values.", + }, + :trusted_node_data => { + :default => false, + :type => :boolean, + :desc => "Stores trusted node data in a hash called $trusted. + When true also prevents $trusted from being overridden in any scope.", } ) Puppet.define_settings(:module_tool, @@ -472,6 +500,36 @@ have a pool of multiple load balanced masters, or for the same master to respond on two physically separate networks under different names. EOT }, + :csr_attributes => { + :default => "$confdir/csr_attributes.yaml", + :type => :file, + :desc => <<EOT +An optional file containing custom attributes to add to certificate signing +requests (CSRs). You should ensure that this file does not exist on your CA +puppet master; if it does, unwanted certificate extensions may leak into +certificates created with the `puppet cert generate` command. + +If present, this file must be a YAML hash containing a `custom_attributes` key +and/or an `extension_requests` key. The value of each key must be a hash, where +each key is a valid OID and each value is an object that can be cast to a string. + +Custom attributes can be used by the CA when deciding whether to sign the +certificate, but are then discarded. Attribute OIDs can be any OID value except +the standard CSR attributes (i.e. attributes described in RFC 2985 section 5.4). +This is useful for embedding a pre-shared key for autosigning policy executables +(see the `autosign` setting), often by using the `1.2.840.113549.1.9.7` +("challenge password") OID. + +Extension requests will be permanently embedded in the final certificate. +Extension OIDs must be in the "ppRegCertExt" (`1.3.6.1.4.1.34380.1.1`) or +"ppPrivCertExt" (`1.3.6.1.4.1.34380.1.2`) OID arcs. The ppRegCertExt arc is +reserved for four of the most common pieces of data to embed: `pp_uuid` (`.1`), +`pp_instance_id` (`.2`), `pp_image_name` (`.3`), and `pp_preshared_key` (`.4`) +--- in the YAML file, these can be referred to by their short descriptive names +instead of their full OID. The ppPrivCertExt arc is unregulated, and can be used +for site-specific extensions. +EOT + }, :certdir => { :default => "$ssldir/certs", :type => :directory, @@ -558,19 +616,19 @@ EOT :type => :file, :mode => 0644, :owner => "service", - :desc => "Certificate authorities who issue server certificates. SSL servers will not be \n" << - "considered authentic unless they posses a certificate issued by an authority \n" << - "listed in this file. If this setting has no value then the Puppet master's CA \n" << - "certificate (localcacert) will be used." + :desc => "Certificate authorities who issue server certificates. SSL servers will not be + considered authentic unless they posses a certificate issued by an authority + listed in this file. If this setting has no value then the Puppet master's CA + certificate (localcacert) will be used." }, :ssl_server_ca_auth => { :type => :file, :mode => 0644, :owner => "service", - :desc => "Certificate authorities who issue client certificates. SSL clients will not be \n" << - "considered authentic unless they posses a certificate issued by an authority \n" << - "listed in this file. If this setting has no value then the Puppet master's CA \n" << - "certificate (localcacert) will be used." + :desc => "Certificate authorities who issue client certificates. SSL clients will not be + considered authentic unless they posses a certificate issued by an authority + listed in this file. If this setting has no value then the Puppet master's CA + certificate (localcacert) will be used." }, :hostcrl => { :default => "$ssldir/crl.pem", @@ -583,8 +641,9 @@ EOT :certificate_revocation => { :default => true, :type => :boolean, - :desc => "Whether certificate revocation should be supported by downloading a Certificate Revocation List (CRL) - to all clients. If enabled, CA chaining will almost definitely not work.", + :desc => "Whether certificate revocation should be supported by downloading a + Certificate Revocation List (CRL) + to all clients. If enabled, CA chaining will almost definitely not work.", }, :certificate_expire_warning => { :default => "60d", @@ -681,12 +740,32 @@ EOT }, :autosign => { :default => "$confdir/autosign.conf", - :type => :file, - :mode => 0644, - :desc => "Whether to enable autosign. Valid values are true (which - autosigns any key request, and is a very bad idea), false (which - never autosigns any key request), and the path to a file, which - uses that configuration file to determine which keys to sign."}, + :type => :autosign, + :desc => "Whether (and how) to autosign certificate requests. This setting + is only relevant on a puppet master acting as a certificate authority (CA). + + Valid values are true (autosigns all certificate requests; not recommended), + false (disables autosigning certificates), or the absolute path to a file. + + The file specified in this setting may be either a **configuration file** + or a **custom policy executable.** Puppet will automatically determine + what it is: If the Puppet user (see the `user` setting) can execute the + file, it will be treated as a policy executable; otherwise, it will be + treated as a config file. + + If a custom policy executable is configured, the CA puppet master will run it + every time it receives a CSR. The executable will be passed the subject CN of the + request _as a command line argument,_ and the contents of the CSR in PEM format + _on stdin._ It should exit with a status of 0 if the cert should be autosigned + and non-zero if the cert should not be autosigned. + + If a certificate request is not autosigned, it will persist for review. An admin + user can use the `puppet cert sign` command to manually sign it, or can delete + the request. + + For info on autosign configuration files, see + [the guide to Puppet's config files](http://docs.puppetlabs.com/guides/configuring.html).", + }, :allow_duplicate_certs => { :default => false, :type => :boolean, @@ -733,10 +812,10 @@ EOT :pidfile => { :type => :file, :default => "$rundir/${run_mode}.pid", - :desc => "The file containing the PID of a running process. " << - "This file is intended to be used by service management " << - "frameworks and monitoring systems to determine if a " << - "puppet process is still in the process table.", + :desc => "The file containing the PID of a running process. + This file is intended to be used by service management frameworks + and monitoring systems to determine if a puppet process is still in + the process table.", }, :bindaddress => { :default => "0.0.0.0", @@ -825,8 +904,9 @@ EOT :modulepath => { :default => "$confdir/modules#{File::PATH_SEPARATOR}/usr/share/puppet/modules", :type => :path, - :desc => "The search path for modules, as a list of directories separated by the system path separator character. " + - "(The POSIX path separator is ':', and the Windows path separator is ';'.)", + :desc => "The search path for modules, as a list of directories separated by the system + path separator character. (The POSIX path separator is ':', and the + Windows path separator is ';'.)", }, :ssl_client_header => { :default => "HTTP_X_CLIENT_DN", @@ -1025,6 +1105,12 @@ EOT :desc => "Boolean; whether puppet agent should ignore schedules. This is useful for initial puppet agent runs.", }, + :default_schedules => { + :default => true, + :type => :boolean, + :desc => "Boolean; whether to generate the default schedule resources. Setting this to + false is useful for keeping external report processors clean of skipped schedule resources.", + }, :puppetport => { :default => 8139, :desc => "Which port puppet agent listens on.", @@ -1114,49 +1200,50 @@ EOT :agent_catalog_run_lockfile => { :default => "$statedir/agent_catalog_run.lock", :type => :string, # (#2888) Ensure this file is not added to the settings catalog. - :desc => "A lock file to indicate that a puppet agent catalog run is currently in progress. " + - "The file contains the pid of the process that holds the lock on the catalog run.", + :desc => "A lock file to indicate that a puppet agent catalog run is currently in progress. + The file contains the pid of the process that holds the lock on the catalog run.", }, :agent_disabled_lockfile => { :default => "$statedir/agent_disabled.lock", - :type => :file, - :desc => "A lock file to indicate that puppet agent runs have been administratively disabled. File contains a JSON object with state information.", + :type => :file, + :desc => "A lock file to indicate that puppet agent runs have been administratively + disabled. File contains a JSON object with state information.", }, :usecacheonfailure => { :default => true, :type => :boolean, :desc => "Whether to use the cached configuration when the remote - configuration will not compile. This option is useful for testing - new configurations, where you want to fix the broken configuration - rather than reverting to a known-good one.", + configuration will not compile. This option is useful for testing + new configurations, where you want to fix the broken configuration + rather than reverting to a known-good one.", }, :use_cached_catalog => { :default => false, :type => :boolean, :desc => "Whether to only use the cached catalog rather than compiling a new catalog - on every run. Puppet can be run with this enabled by default and then selectively - disabled when a recompile is desired.", + on every run. Puppet can be run with this enabled by default and then selectively + disabled when a recompile is desired.", }, :ignoremissingtypes => { :default => false, :type => :boolean, :desc => "Skip searching for classes and definitions that were missing during a - prior compilation. The list of missing objects is maintained per-environment and - persists until the environment is cleared or the master is restarted.", + prior compilation. The list of missing objects is maintained per-environment and + persists until the environment is cleared or the master is restarted.", }, :ignorecache => { :default => false, :type => :boolean, :desc => "Ignore cache and always recompile the configuration. This is - useful for testing new configurations, where the local cache may in - fact be stale even if the timestamps are up to date - if the facts - change or if the server changes.", + useful for testing new configurations, where the local cache may in + fact be stale even if the timestamps are up to date - if the facts + change or if the server changes.", }, :dynamicfacts => { :default => "memorysize,memoryfree,swapsize,swapfree", :desc => "(Deprecated) Facts that are dynamic; these facts will be ignored when deciding whether - changed facts should result in a recompile. Multiple facts should be - comma-separated.", + changed facts should result in a recompile. Multiple facts should be + comma-separated.", :hook => proc { |value| if value Puppet.deprecation_warning "The dynamicfacts setting is deprecated and will be ignored." @@ -1167,13 +1254,13 @@ EOT :default => "$runinterval", :type => :duration, :desc => "The maximum time to delay before runs. Defaults to being the same as the - run interval. #{AS_DURATION}", + run interval. #{AS_DURATION}", }, :splay => { :default => false, :type => :boolean, :desc => "Whether to sleep for a pseudo-random (but consistent) amount of time before - a run.", + a run.", }, :clientbucketdir => { :default => "$vardir/clientbucket", @@ -1185,8 +1272,8 @@ EOT :default => "2m", :type => :duration, :desc => "How long the client should wait for the configuration to be retrieved - before considering it a failure. This can help reduce flapping if too - many clients contact the server at one time. #{AS_DURATION}", + before considering it a failure. This can help reduce flapping if too + many clients contact the server at one time. #{AS_DURATION}", }, :report_server => { :default => "$server", @@ -1225,8 +1312,8 @@ EOT :default => false, :type => :boolean, :desc => "Whether to create dot graph files for the different - configuration graphs. These dot files can be interpreted by tools - like OmniGraffle or dot (which is part of ImageMagick).", + configuration graphs. These dot files can be interpreted by tools + like OmniGraffle or dot (which is part of ImageMagick).", }, :graphdir => { :default => "$statedir/graphs", @@ -1237,11 +1324,12 @@ EOT :default => false, :type => :boolean, :desc => "Allow http compression in REST communication with the master. - This setting might improve performance for agent -> master communications over slow WANs. - Your puppet master needs to support compression (usually by activating some settings in a reverse-proxy - in front of the puppet master, which rules out webrick). - It is harmless to activate this settings if your master doesn't support - compression, but if it supports it, this setting might reduce performance on high-speed LANs.", + This setting might improve performance for agent -> master + communications over slow WANs. Your puppet master needs to support + compression (usually by activating some settings in a reverse-proxy in + front of the puppet master, which rules out webrick). It is harmless to + activate this settings if your master doesn't support compression, but + if it supports it, this setting might reduce performance on high-speed LANs.", }, :waitforcert => { :default => "2m", @@ -1310,6 +1398,15 @@ EOT is used for retrieval, so anything that is a valid file source can be used here.", }, + :pluginfactdest => { + :type => :directory, + :default => "$vardir/facts.d", + :desc => "Where Puppet should store external facts that are being handled by pluginsync", + }, + :pluginfactsource => { + :default => "puppet://$server/pluginfacts", + :desc => "Where to retrieve external facts for pluginsync", + }, :pluginsync => { :default => true, :type => :boolean, @@ -1330,7 +1427,8 @@ EOT :type => :path, :default => "$vardir/lib/facter#{File::PATH_SEPARATOR}$vardir/facts", :desc => "Where Puppet should look for facts. Multiple directories should - be separated by the system path separator character. (The POSIX path separator is ':', and the Windows path separator is ';'.)", + be separated by the system path separator character. (The POSIX path + separator is ':', and the Windows path separator is ';'.)", :call_hook => :on_initialize_and_write, # Call our hook with the default value, so we always get the value added to facter. :hook => proc { |value| Facter.search(value) if Facter.respond_to?(:search) }} @@ -1400,27 +1498,27 @@ EOT :dbport => { :default => "", :desc => "The database password for caching. Only - used when networked databases are used. #{STORECONFIGS_ONLY}", + used when networked databases are used. #{STORECONFIGS_ONLY}", }, :dbuser => { :default => "puppet", :desc => "The database user for caching. Only - used when networked databases are used. #{STORECONFIGS_ONLY}", + used when networked databases are used. #{STORECONFIGS_ONLY}", }, :dbpassword => { :default => "puppet", :desc => "The database password for caching. Only - used when networked databases are used. #{STORECONFIGS_ONLY}", + used when networked databases are used. #{STORECONFIGS_ONLY}", }, :dbconnections => { :default => '', :desc => "The number of database connections for networked - databases. Will be ignored unless the value is a positive integer. #{STORECONFIGS_ONLY}", + databases. Will be ignored unless the value is a positive integer. #{STORECONFIGS_ONLY}", }, :dbsocket => { :default => "", :desc => "The database socket location. Only used when networked - databases are used. Will be ignored if the value is an empty string. #{STORECONFIGS_ONLY}", + databases are used. Will be ignored if the value is an empty string. #{STORECONFIGS_ONLY}", }, :railslog => { :default => "$logdir/rails.log", @@ -1434,8 +1532,8 @@ EOT :rails_loglevel => { :default => "info", :desc => "The log level for Rails connections. The value must be - a valid log level within Rails. Production environments normally use `info` - and other environments normally use `debug`. #{STORECONFIGS_ONLY}", + a valid log level within Rails. Production environments normally use `info` + and other environments normally use `debug`. #{STORECONFIGS_ONLY}", } ) @@ -1445,7 +1543,7 @@ EOT :couchdb_url => { :default => "http://127.0.0.1:5984/puppet", :desc => "The url where the puppet couchdb database will be created. - Only used when `facts_terminus` is set to `couch`.", + Only used when `facts_terminus` is set to `couch`.", } ) @@ -1454,15 +1552,15 @@ EOT :tags => { :default => "", :desc => "Tags to use to find resources. If this is set, then - only resources tagged with the specified tags will be applied. - Values must be comma-separated.", + only resources tagged with the specified tags will be applied. + Values must be comma-separated.", }, :evaltrace => { :default => false, :type => :boolean, :desc => "Whether each resource should log when it is - being evaluated. This allows you to interactively see exactly - what is being done.", + being evaluated. This allows you to interactively see exactly + what is being done.", }, :summarize => { :default => false, @@ -1476,13 +1574,13 @@ EOT :external_nodes => { :default => "none", :desc => "An external command that can produce node information. The command's output - must be a YAML dump of a hash, and that hash must have a `classes` key and/or - a `parameters` key, where `classes` is an array or hash and - `parameters` is a hash. For unknown nodes, the command should - exit with a non-zero exit code. + must be a YAML dump of a hash, and that hash must have a `classes` key and/or + a `parameters` key, where `classes` is an array or hash and + `parameters` is a hash. For unknown nodes, the command should + exit with a non-zero exit code. - This command makes it straightforward to store your node mapping - information in other data sources like databases.", + This command makes it straightforward to store your node mapping + information in other data sources like databases.", } ) @@ -1492,15 +1590,15 @@ EOT :default => false, :type => :boolean, :desc => "Whether SSL should be used when searching for nodes. - Defaults to false because SSL usually requires certificates - to be set up on the client side.", + Defaults to false because SSL usually requires certificates + to be set up on the client side.", }, :ldaptls => { :default => false, :type => :boolean, :desc => "Whether TLS should be used when searching for nodes. - Defaults to false because TLS usually requires certificates - to be set up on the client side.", + Defaults to false because TLS usually requires certificates + to be set up on the client side.", }, :ldapserver => { :default => "ldap", @@ -1518,20 +1616,20 @@ EOT :ldapclassattrs => { :default => "puppetclass", :desc => "The LDAP attributes to use to define Puppet classes. Values - should be comma-separated.", + should be comma-separated.", }, :ldapstackedattrs => { :default => "puppetvar", :desc => "The LDAP attributes that should be stacked to arrays by adding - the values in all hierarchy elements of the tree. Values - should be comma-separated.", + the values in all hierarchy elements of the tree. Values + should be comma-separated.", }, :ldapattrs => { :default => "all", :desc => "The LDAP attributes to include when querying LDAP for nodes. All - returned attributes are set as variables in the top-level scope. - Multiple values should be comma-separated. The value 'all' returns - all attributes.", + returned attributes are set as variables in the top-level scope. + Multiple values should be comma-separated. The value 'all' returns + all attributes.", }, :ldapparentattr => { :default => "parentnode", @@ -1540,7 +1638,7 @@ EOT :ldapuser => { :default => "", :desc => "The user to use to connect to LDAP. Must be specified as a - full DN.", + full DN.", }, :ldappassword => { :default => "", @@ -1549,9 +1647,9 @@ EOT :ldapbase => { :default => "", :desc => "The search base for LDAP searches. It's impossible to provide - a meaningful default here, although the LDAP libraries might - have one already set. Generally, it should be the 'ou=Hosts' - branch under your main directory.", + a meaningful default here, although the LDAP libraries might + have one already set. Generally, it should be the 'ou=Hosts' + branch under your main directory.", } ) @@ -1560,13 +1658,13 @@ EOT :default => false, :type => :boolean, :desc => "Whether to store each client's configuration, including catalogs, facts, -and related data. This also enables the import and export of resources in -the Puppet language - a mechanism for exchange resources between nodes. + and related data. This also enables the import and export of resources in + the Puppet language - a mechanism for exchange resources between nodes. -By default this uses ActiveRecord and an SQL database to store and query -the data; this, in turn, will depend on Rails being available. + By default this uses ActiveRecord and an SQL database to store and query + the data; this, in turn, will depend on Rails being available. -You can adjust the backend using the storeconfigs_backend setting.", + You can adjust the backend using the storeconfigs_backend setting.", # Call our hook with the default value, so we always get the libdir set. :call_hook => :on_initialize_and_write, :hook => proc do |value| @@ -1587,8 +1685,8 @@ You can adjust the backend using the storeconfigs_backend setting.", :type => :terminus, :default => "active_record", :desc => "Configure the backend terminus used for StoreConfigs. -By default, this uses the ActiveRecord store, which directly talks to the -database from within the Puppet Master process." + By default, this uses the ActiveRecord store, which directly talks to the + database from within the Puppet Master process." } ) @@ -1597,57 +1695,63 @@ database from within the Puppet Master process." :default => "$vardir/templates", :type => :directory, :desc => "Where Puppet looks for template files. Can be a list of colon-separated - directories.", + directories.", }, :allow_variables_with_dashes => { :default => false, :desc => <<-'EOT' -Permit hyphens (`-`) in variable names and issue deprecation warnings about -them. This setting **should always be `false`;** setting it to `true` -will cause subtle and wide-ranging bugs. It will be removed in a future version. - -Hyphenated variables caused major problems in the language, but were allowed -between Puppet 2.7.3 and 2.7.14. If you used them during this window, we -apologize for the inconvenience --- you can temporarily set this to `true` -in order to upgrade, and can rename your variables at your leisure. Please -revert it to `false` after you have renamed all affected variables. -EOT + Permit hyphens (`-`) in variable names and issue deprecation warnings about + them. This setting **should always be `false`;** setting it to `true` + will cause subtle and wide-ranging bugs. It will be removed in a future version. + + Hyphenated variables caused major problems in the language, but were allowed + between Puppet 2.7.3 and 2.7.14. If you used them during this window, we + apologize for the inconvenience --- you can temporarily set this to `true` + in order to upgrade, and can rename your variables at your leisure. Please + revert it to `false` after you have renamed all affected variables. + EOT }, :parser => { :default => "current", :desc => <<-'EOT' -Selects the parser to use for parsing puppet manifests (in puppet DSL language/'.pp' files). -Available choices are 'current' (the default), and 'future'. + Selects the parser to use for parsing puppet manifests (in puppet DSL + language/'.pp' files). Available choices are `current` (the default), + and `future`. -The 'curent' parser means that the released version of the parser should be used. + The `curent` parser means that the released version of the parser should + be used. -The 'future' parser is a "time travel to the future" allowing early exposure to new language features. -What these fatures are will vary from release to release and they may be invididually configurable. + The `future` parser is a "time travel to the future" allowing early + exposure to new language features. What these fatures are will vary from + release to release and they may be invididually configurable. -Available Since Puppet 3.2. -EOT + Available Since Puppet 3.2. + EOT }, :max_errors => { :default => 10, :desc => <<-'EOT' -Sets the max number of logged/displayed parser validation errors in case multiple errors have been detected. -A value of 0 is the same as value 1. The count is per manifest. -EOT + Sets the max number of logged/displayed parser validation errors in case + multiple errors have been detected. A value of 0 is the same as value 1. + The count is per manifest. + EOT }, :max_warnings => { :default => 10, :desc => <<-'EOT' -Sets the max number of logged/displayed parser validation warnings in case multiple errors have been detected. -A value of 0 is the same as value 1. The count is per manifest. -EOT + Sets the max number of logged/displayed parser validation warnings in + case multiple errors have been detected. A value of 0 is the same as + value 1. The count is per manifest. + EOT }, :max_deprecations => { :default => 10, :desc => <<-'EOT' -Sets the max number of logged/displayed parser validation deprecation warnings in case multiple errors have been detected. -A value of 0 is the same as value 1. The count is per manifest. -EOT + Sets the max number of logged/displayed parser validation deprecation + warnings in case multiple errors have been detected. A value of 0 is the + same as value 1. The count is per manifest. + EOT } ) @@ -1656,7 +1760,7 @@ EOT :default => false, :type => :boolean, :desc => "Whether to document all resources when using `puppet doc` to - generate manifest documentation.", + generate manifest documentation.", } ) end diff --git a/lib/puppet/error.rb b/lib/puppet/error.rb index 15952662e..34da17219 100644 --- a/lib/puppet/error.rb +++ b/lib/puppet/error.rb @@ -56,5 +56,6 @@ module Puppet # An error class for when I don't know what happened. Automatically # prints a stack trace when in debug mode. class DevError < Puppet::Error + include ExternalFileError end end diff --git a/lib/puppet/external/lock.rb b/lib/puppet/external/lock.rb deleted file mode 100644 index 024fedf3d..000000000 --- a/lib/puppet/external/lock.rb +++ /dev/null @@ -1,63 +0,0 @@ -require 'thread' -require 'sync' - -# Gotten from: -# http://path.berkeley.edu/~vjoel/ruby/solaris-bug.rb - -# Extensions to the File class for exception-safe file locking in a -# environment with multiple user threads. - -# This is here because closing a file on solaris unlocks any locks that -# other threads might have. So we have to make sure that only the last -# reader thread closes the file. -# -# The hash maps inode number to a count of reader threads -$reader_count = Hash.new(0) - -class File - # Get an exclusive (i.e., write) lock on the file, and yield to the block. - # If the lock is not available, wait for it without blocking other ruby - # threads. - def lock_exclusive - if Thread.list.size == 1 - flock(LOCK_EX) - else - # ugly hack because waiting for a lock in a Ruby thread blocks the - # process - period = 0.001 - until flock(LOCK_EX|LOCK_NB) - sleep period - period *= 2 if period < 1 - end - end - - yield self - ensure - flush - flock(LOCK_UN) - end - - # Get a shared (i.e., read) lock on the file, and yield to the block. - # If the lock is not available, wait for it without blocking other ruby - # threads. - def lock_shared - if Thread.list.size == 1 - flock(LOCK_SH) - else - # ugly hack because waiting for a lock in a Ruby thread blocks the - # process - period = 0.001 - until flock(LOCK_SH|LOCK_NB) - sleep period - period *= 2 if period < 1 - end - end - - yield self - ensure - Thread.exclusive {flock(LOCK_UN) if $reader_count[self.stat.ino] == 1} - ## for solaris, no need to unlock here--closing does it - ## but this has no effect on the bug - end -end - diff --git a/lib/puppet/external/nagios.rb b/lib/puppet/external/nagios.rb index 2ed829198..582779441 100755..100644 --- a/lib/puppet/external/nagios.rb +++ b/lib/puppet/external/nagios.rb @@ -1,5 +1,3 @@ -#!/usr/bin/env ruby -w - #-------------------- # A script to retrieve hosts from ldap and create an importable # cfservd file from them diff --git a/lib/puppet/external/nagios/base.rb b/lib/puppet/external/nagios/base.rb index ce75f66ea..0aa50b411 100755..100644 --- a/lib/puppet/external/nagios/base.rb +++ b/lib/puppet/external/nagios/base.rb @@ -297,12 +297,13 @@ class Nagios::Base def to_s str = "define #{self.type} {\n" - self.each { |param,value| + @parameters.keys.sort.each { |param| + value = @parameters[param] str += %{\t%-30s %s\n} % [ param, if value.is_a? Array - value.join(",") + value.join(",").sub(';', '\;') else - value + value.sub(';', '\;') end ] } diff --git a/lib/puppet/external/nagios/grammar.ry b/lib/puppet/external/nagios/grammar.ry index 82f0a907c..d6bbc78ec 100644 --- a/lib/puppet/external/nagios/grammar.ry +++ b/lib/puppet/external/nagios/grammar.ry @@ -1,7 +1,7 @@ # vim: syntax=ruby class Nagios::Parser -token DEFINE NAME STRING PARAM LCURLY RCURLY VALUE RETURN COMMENT INLINECOMMENT +token DEFINE NAME PARAM LCURLY RCURLY VALUE RETURN rule decls: decl { return val[0] if val[0] } @@ -20,13 +20,9 @@ decls: decl { return val[0] if val[0] } decl: object { result = [val[0]] } | RETURN { result = nil } - | comment ; -comment: COMMENT RETURN { result = nil } - ; - -object: DEFINE NAME LCURLY RETURN vars RCURLY { +object: DEFINE NAME LCURLY returns vars RCURLY { result = Nagios::Base.create(val[1],val[4]) } ; @@ -40,146 +36,211 @@ vars: var } ; -var: PARAM VALUE icomment returns { result = {val[0] => val[1]} } +var: PARAM VALUE returns { result = {val[0] => val[1]} } ; -returns: RETURN - | returns RETURN - ; - -icomment: # nothing - | INLINECOMMENT +returns: RETURN + | RETURN returns ; end ----inner +require 'strscan' class ::Nagios::Parser::SyntaxError < RuntimeError; end def parse(src) - @src = src + if src.respond_to?("force_encoding") then + src.force_encoding("ASCII-8BIT") + end + @ss = StringScanner.new(src) - # state variables - @invar = false - @inobject = false - @done = false + # state variables + @in_parameter_value = false + @in_object_definition = false + @done = false - @line = 0 - @yydebug = true + @line = 1 + @yydebug = true - do_parse + do_parse end -# The lexer. Very simple. -def token - @src.sub!(/\A\n/,'') - if $& - @line += 1 - return [ :RETURN, "\n" ] - end +# This tokenizes the outside of object definitions, +# and detects when we start defining an object. +# We ignore whitespaces, comments and inline comments. +# We yield when finding newlines, the "define" keyword, +# the object name and the opening curly bracket. +def tokenize_outside_definitions + case + when (chars = @ss.skip(/[ \t]+/)) # ignore whitespace /\s+/ + ; - if @done - return nil - end - yytext = String.new + when (text = @ss.scan(/\#.*$/)) # ignore comments + ; + when (text = @ss.scan(/;.*$/)) # ignore inline comments + ; - # remove comments from this line - @src.sub!(/\A[ \t]*;.*\n/,"\n") - if $& - return [:INLINECOMMENT, ""] - end + when (text = @ss.scan(/\n/)) # newline + [:RETURN, text] - @src.sub!(/\A#.*\n/,"\n") - if $& - return [:COMMENT, ""] - end + when (text = @ss.scan(/\b(define)\b/)) # the "define" keyword + [:DEFINE, text] - @src.sub!(/#.*/,'') + when (text = @ss.scan(/[^{ \t\n]+/)) # the name of the object being defined (everything not an opening curly bracket or a separator) + [:NAME, text] - if @src.length == 0 - @done = true - return [false, '$'] - end + when (text = @ss.scan(/\{/)) # the opening curly bracket - we enter object definition + @in_object_definition = true + [:LCURLY, text] - if @invar - @src.sub!(/\A[ \t]+/,'') - @src.sub!(/\A([^;\n]+)(\n|;)/,'\2') - if $1 - yytext += $1 - end - @invar = false - return [:VALUE, yytext] - else - @src.sub!(/\A[\t ]*(\S+)([\t ]*|$)/,'') - if $1 - yytext = $1 - case yytext - when 'define' - #puts "got define" - return [:DEFINE, yytext] - when '{' - #puts "got {" - @inobject = true - return [:LCURLY, yytext] - else - unless @inobject - #puts "got type: #{yytext}" - if yytext =~ /\W/ - giveback = yytext.dup - giveback.sub!(/^\w+/,'') - #puts "giveback " + giveback - #puts "yytext " + yytext - yytext.sub!(/\W.*$/,'') - #puts "yytext " + yytext - #puts "all [#{giveback} #{yytext} #{orig}]" - @src = giveback + @src - end - return [:NAME, yytext] - else - if yytext == '}' - #puts "got closure: #{yytext}" - @inobject = false - return [:RCURLY, '}'] - end - - unless @invar - @invar = true - return [:PARAM, $1] - else - end - end - end - end + else + text = @ss.string[@ss.pos .. -1] + raise ScanError, "can not match: '#{text}'" + end # case +end + +# This tokenizes until we find the parameter name. +def tokenize_parameter_name + case + when (chars = @ss.skip(/[ \t]+/)) # ignore whitespace /\s+/ + ; + + when (text = @ss.scan(/\#.*$/)) # ignore comments + ; + + when (text = @ss.scan(/;.*$/)) # ignore inline comments + ; + + when (text = @ss.scan(/\n/)) # newline + [:RETURN, text] + + when (text = @ss.scan(/\}/)) # closing curly bracket : end of definition + @in_object_definition = false + [:RCURLY, text] + + when (not @in_parameter_value and (text = @ss.scan(/\S+/))) # This is the name of the parameter + @in_parameter_value = true + [:PARAM, text] + + else + text = @ss.string[@ss.pos .. -1] + raise ScanError, "can not match: '#{text}'" + end # case +end + +# This tokenizes the parameter value. +# There is a special handling for lines containing semicolons : +# - unescaped semicolons are line comments (and should stop parsing of the line) +# - escaped (with backslash \) semicolons should be kept in the parameter value (without the backslash) +def tokenize_parameter_value + case + when (chars = @ss.skip(/[ \t]+/)) # ignore whitespace /\s+/ + ; + + when (text = @ss.scan(/\#.*$/)) # ignore comments + ; + + when (text = @ss.scan(/\n/)) # newline + [:RETURN, text] + + when (text = @ss.scan(/.+$/)) # Value of parameter + @in_parameter_value = false + + # Special handling of inline comments (;) and escaped semicolons (\;) + + # We split the string on escaped semicolons (\;), + # Then we rebuild it as long as there are no inline comments (;) + # We join the rebuilt string with unescaped semicolons (on purpose) + array = text.split('\;', 0) + + text = "" + + array.each do |elt| + + # Now we split at inline comments. If we have more than 1 element in the array + # it means we have an inline comment, so we are able to stop parsing + # However we still want to reconstruct the string with its first part (before the comment) + linearray = elt.split(';', 0) + + # Let's reconstruct the string with a (unescaped) semicolon + if text != "" then + text += ';' + end + text += linearray[0] + + # Now we can stop + if linearray.length > 1 then + break + end end + + + # We strip the text to remove spaces between end of string and beginning of inline comment + [:VALUE, text.strip] + + else + text = @ss.string[@ss.pos .. -1] + raise ScanError, "can not match: '#{text}'" + end # case +end + +# This tokenizes inside an object definition. +# Two cases : parameter name and parameter value +def tokenize_inside_definitions + if @in_parameter_value + tokenize_parameter_value + else + tokenize_parameter_name + end +end + +# The lexer. Very simple. +def token + text = @ss.peek(1) + @line += 1 if text == "\n" + + token = if @in_object_definition + tokenize_inside_definitions + else + tokenize_outside_definitions + end + token end def next_token - token + return if @ss.eos? + + # skips empty actions + until _next_token = token or @ss.eos?; end + _next_token end def yydebug - 1 + 1 end def yywrap - 0 + 0 end def on_error(token, value, vstack ) - msg = "" - unless value.nil? - msg = "line #{@line}: syntax error at '#{value}'" - else - msg = "line #{@line}: syntax error at '#{token}'" - end - unless @src.size > 0 - msg = "line #{@line}: Unexpected end of file" - end - if token == '$end'.intern - puts "okay, this is silly" - else - raise ::Nagios::Parser::SyntaxError, msg - end + # text = @ss.string[@ss.pos .. -1] + text = @ss.peek(20) + msg = "" + unless value.nil? + msg = "line #{@line}: syntax error at value '#{value}' : #{text}" + else + msg = "line #{@line}: syntax error at token '#{token}' : #{text}" + end + if @ss.eos? + msg = "line #{@line}: Unexpected end of file" + end + if token == '$end'.intern + puts "okay, this is silly" + else + raise ::Nagios::Parser::SyntaxError, msg + end end diff --git a/lib/puppet/external/nagios/parser.rb b/lib/puppet/external/nagios/parser.rb index e89192b3a..9a82ffdb1 100644 --- a/lib/puppet/external/nagios/parser.rb +++ b/lib/puppet/external/nagios/parser.rb @@ -8,205 +8,269 @@ require 'racc/parser.rb' module Nagios class Parser < Racc::Parser -module_eval(<<'...end grammar.ry/module_eval...', 'grammar.ry', 57) +module_eval(<<'...end grammar.ry/module_eval...', 'grammar.ry', 49) +require 'strscan' class ::Nagios::Parser::SyntaxError < RuntimeError; end def parse(src) - @src = src + if src.respond_to?("force_encoding") then + src.force_encoding("ASCII-8BIT") + end + @ss = StringScanner.new(src) - # state variables - @invar = false - @inobject = false - @done = false + # state variables + @in_parameter_value = false + @in_object_definition = false + @done = false - @line = 0 - @yydebug = true + @line = 1 + @yydebug = true - do_parse + do_parse end -# The lexer. Very simple. -def token - @src.sub!(/\A\n/,'') - if $& - @line += 1 - return [ :RETURN, "\n" ] - end +# This tokenizes the outside of object definitions, +# and detects when we start defining an object. +# We ignore whitespaces, comments and inline comments. +# We yield when finding newlines, the "define" keyword, +# the object name and the opening curly bracket. +def tokenize_outside_definitions + case + when (chars = @ss.skip(/[ \t]+/)) # ignore whitespace /\s+/ + ; - if @done - return nil - end - yytext = String.new + when (text = @ss.scan(/\#.*$/)) # ignore comments + ; + when (text = @ss.scan(/;.*$/)) # ignore inline comments + ; - # remove comments from this line - @src.sub!(/\A[ \t]*;.*\n/,"\n") - if $& - return [:INLINECOMMENT, ""] - end + when (text = @ss.scan(/\n/)) # newline + [:RETURN, text] - @src.sub!(/\A#.*\n/,"\n") - if $& - return [:COMMENT, ""] - end + when (text = @ss.scan(/\b(define)\b/)) # the "define" keyword + [:DEFINE, text] - @src.sub!(/#.*/,'') + when (text = @ss.scan(/[^{ \t\n]+/)) # the name of the object being defined (everything not an opening curly bracket or a separator) + [:NAME, text] - if @src.length == 0 - @done = true - return [false, '$'] - end + when (text = @ss.scan(/\{/)) # the opening curly bracket - we enter object definition + @in_object_definition = true + [:LCURLY, text] - if @invar - @src.sub!(/\A[ \t]+/,'') - @src.sub!(/\A([^;\n]+)(\n|;)/,'\2') - if $1 - yytext += $1 - end - @invar = false - return [:VALUE, yytext] - else - @src.sub!(/\A[\t ]*(\S+)([\t ]*|$)/,'') - if $1 - yytext = $1 - case yytext - when 'define' - #puts "got define" - return [:DEFINE, yytext] - when '{' - #puts "got {" - @inobject = true - return [:LCURLY, yytext] - else - unless @inobject - #puts "got type: #{yytext}" - if yytext =~ /\W/ - giveback = yytext.dup - giveback.sub!(/^\w+/,'') - #puts "giveback " + giveback - #puts "yytext " + yytext - yytext.sub!(/\W.*$/,'') - #puts "yytext " + yytext - #puts "all [#{giveback} #{yytext} #{orig}]" - @src = giveback + @src - end - return [:NAME, yytext] - else - if yytext == '}' - #puts "got closure: #{yytext}" - @inobject = false - return [:RCURLY, '}'] - end - - unless @invar - @invar = true - return [:PARAM, $1] - else - end - end - end - end + else + text = @ss.string[@ss.pos .. -1] + raise ScanError, "can not match: '#{text}'" + end # case +end + +# This tokenizes until we find the parameter name. +def tokenize_parameter_name + case + when (chars = @ss.skip(/[ \t]+/)) # ignore whitespace /\s+/ + ; + + when (text = @ss.scan(/\#.*$/)) # ignore comments + ; + + when (text = @ss.scan(/;.*$/)) # ignore inline comments + ; + + when (text = @ss.scan(/\n/)) # newline + [:RETURN, text] + + when (text = @ss.scan(/\}/)) # closing curly bracket : end of definition + @in_object_definition = false + [:RCURLY, text] + + when (not @in_parameter_value and (text = @ss.scan(/\S+/))) # This is the name of the parameter + @in_parameter_value = true + [:PARAM, text] + + else + text = @ss.string[@ss.pos .. -1] + raise ScanError, "can not match: '#{text}'" + end # case +end + +# This tokenizes the parameter value. +# There is a special handling for lines containing semicolons : +# - unescaped semicolons are line comments (and should stop parsing of the line) +# - escaped (with backslash \) semicolons should be kept in the parameter value (without the backslash) +def tokenize_parameter_value + case + when (chars = @ss.skip(/[ \t]+/)) # ignore whitespace /\s+/ + ; + + when (text = @ss.scan(/\#.*$/)) # ignore comments + ; + + when (text = @ss.scan(/\n/)) # newline + [:RETURN, text] + + when (text = @ss.scan(/.+$/)) # Value of parameter + @in_parameter_value = false + + # Special handling of inline comments (;) and escaped semicolons (\;) + + # We split the string on escaped semicolons (\;), + # Then we rebuild it as long as there are no inline comments (;) + # We join the rebuilt string with unescaped semicolons (on purpose) + array = text.split('\;', 0) + + text = "" + + array.each do |elt| + + # Now we split at inline comments. If we have more than 1 element in the array + # it means we have an inline comment, so we are able to stop parsing + # However we still want to reconstruct the string with its first part (before the comment) + linearray = elt.split(';', 0) + + # Let's reconstruct the string with a (unescaped) semicolon + if text != "" then + text += ';' + end + text += linearray[0] + + # Now we can stop + if linearray.length > 1 then + break + end end + + + # We strip the text to remove spaces between end of string and beginning of inline comment + [:VALUE, text.strip] + + else + text = @ss.string[@ss.pos .. -1] + raise ScanError, "can not match: '#{text}'" + end # case +end + +# This tokenizes inside an object definition. +# Two cases : parameter name and parameter value +def tokenize_inside_definitions + if @in_parameter_value + tokenize_parameter_value + else + tokenize_parameter_name + end +end + +# The lexer. Very simple. +def token + text = @ss.peek(1) + @line += 1 if text == "\n" + + token = if @in_object_definition + tokenize_inside_definitions + else + tokenize_outside_definitions + end + token end def next_token - token + return if @ss.eos? + + # skips empty actions + until _next_token = token or @ss.eos?; end + _next_token end def yydebug - 1 + 1 end def yywrap - 0 + 0 end def on_error(token, value, vstack ) - msg = "" - unless value.nil? - msg = "line #{@line}: syntax error at '#{value}'" - else - msg = "line #{@line}: syntax error at '#{token}'" - end - unless @src.size > 0 - msg = "line #{@line}: Unexpected end of file" - end - if token == '$end'.intern - puts "okay, this is silly" - else - raise ::Nagios::Parser::SyntaxError, msg - end + # text = @ss.string[@ss.pos .. -1] + text = @ss.peek(20) + msg = "" + unless value.nil? + msg = "line #{@line}: syntax error at value '#{value}' : #{text}" + else + msg = "line #{@line}: syntax error at token '#{token}' : #{text}" + end + if @ss.eos? + msg = "line #{@line}: Unexpected end of file" + end + if token == '$end'.intern + puts "okay, this is silly" + else + raise ::Nagios::Parser::SyntaxError, msg + end end ...end grammar.ry/module_eval... ##### State transition tables begin ### racc_action_table = [ - 8, 17, 7, 18, 7, 14, 12, 13, 11, 4, - 6, 4, 6, 17, 10, 20, 22, 24, 25 ] + 8, 3, 3, 14, 12, 18, 10, 4, 4, 9, + 14, 12, 6, 19, 12 ] racc_action_check = [ - 1, 15, 1, 15, 0, 13, 8, 11, 7, 1, - 1, 0, 0, 14, 6, 17, 20, 21, 23 ] + 5, 0, 5, 13, 9, 13, 8, 0, 5, 6, + 11, 12, 3, 14, 19 ] racc_action_pointer = [ - 2, 0, nil, nil, nil, nil, 5, 5, 6, nil, - nil, 1, nil, -4, 8, -4, nil, 7, nil, nil, - 5, 8, nil, 9, nil, nil ] + -1, nil, nil, 9, nil, 0, 4, nil, 6, -4, + nil, 6, 3, -1, 6, nil, nil, nil, nil, 6, + nil ] racc_action_default = [ - -15, -15, -1, -3, -4, -5, -15, -15, -15, -2, - -6, -15, 26, -15, -15, -15, -8, -15, -7, -9, - -13, -15, -14, -10, -11, -12 ] + -11, -1, -3, -11, -4, -11, -11, -2, -11, -11, + 21, -11, -9, -11, -11, -6, -10, -7, -5, -11, + -8 ] racc_goto_table = [ - 16, 19, 2, 9, 1, 15, 21, 23 ] + 11, 1, 15, 16, 17, 13, 7, 5, nil, nil, + 20 ] racc_goto_check = [ - 6, 6, 2, 2, 1, 5, 7, 8 ] + 4, 2, 6, 4, 6, 5, 2, 1, nil, nil, + 4 ] racc_goto_pointer = [ - nil, 4, 2, nil, nil, -9, -14, -14, -14 ] + nil, 7, 1, nil, -9, -6, -9 ] racc_goto_default = [ - nil, nil, nil, 3, 5, nil, nil, nil, nil ] + nil, nil, nil, 2, nil, nil, nil ] racc_reduce_table = [ 0, 0, :racc_error, - 1, 13, :_reduce_1, - 2, 13, :_reduce_2, - 1, 14, :_reduce_3, - 1, 14, :_reduce_4, + 1, 10, :_reduce_1, + 2, 10, :_reduce_2, + 1, 11, :_reduce_3, + 1, 11, :_reduce_4, + 6, 12, :_reduce_5, 1, 14, :_reduce_none, - 2, 16, :_reduce_6, - 6, 15, :_reduce_7, - 1, 17, :_reduce_none, - 2, 17, :_reduce_9, - 4, 18, :_reduce_10, - 1, 20, :_reduce_none, - 2, 20, :_reduce_none, - 0, 19, :_reduce_none, - 1, 19, :_reduce_none ] + 2, 14, :_reduce_7, + 3, 15, :_reduce_8, + 1, 13, :_reduce_none, + 2, 13, :_reduce_none ] -racc_reduce_n = 15 +racc_reduce_n = 11 -racc_shift_n = 26 +racc_shift_n = 21 racc_token_table = { false => 0, :error => 1, :DEFINE => 2, :NAME => 3, - :STRING => 4, - :PARAM => 5, - :LCURLY => 6, - :RCURLY => 7, - :VALUE => 8, - :RETURN => 9, - :COMMENT => 10, - :INLINECOMMENT => 11 } + :PARAM => 4, + :LCURLY => 5, + :RCURLY => 6, + :VALUE => 7, + :RETURN => 8 } -racc_nt_base = 12 +racc_nt_base = 9 racc_use_result_var = true @@ -231,23 +295,18 @@ Racc_token_to_s_table = [ "error", "DEFINE", "NAME", - "STRING", "PARAM", "LCURLY", "RCURLY", "VALUE", "RETURN", - "COMMENT", - "INLINECOMMENT", "$start", "decls", "decl", "object", - "comment", + "returns", "vars", - "var", - "icomment", - "returns" ] + "var" ] Racc_debug_parser = false @@ -257,82 +316,72 @@ Racc_debug_parser = false module_eval(<<'.,.,', 'grammar.ry', 6) def _reduce_1(val, _values, result) - return val[0] if val[0] + return val[0] if val[0] result end .,., module_eval(<<'.,.,', 'grammar.ry', 8) def _reduce_2(val, _values, result) - if val[1].nil? - result = val[0] - else - if val[0].nil? - result = val[1] - else - result = [ val[0], val[1] ].flatten - end - end + if val[1].nil? + result = val[0] + else + if val[0].nil? + result = val[1] + else + result = [ val[0], val[1] ].flatten + end + end + result end .,., module_eval(<<'.,.,', 'grammar.ry', 20) def _reduce_3(val, _values, result) - result = [val[0]] + result = [val[0]] result end .,., module_eval(<<'.,.,', 'grammar.ry', 21) def _reduce_4(val, _values, result) - result = nil + result = nil result end .,., -# reduce 5 omitted - module_eval(<<'.,.,', 'grammar.ry', 25) - def _reduce_6(val, _values, result) - result = nil + def _reduce_5(val, _values, result) + result = Nagios::Base.create(val[1],val[4]) + result end .,., -module_eval(<<'.,.,', 'grammar.ry', 29) - def _reduce_7(val, _values, result) - result = Nagios::Base.create(val[1],val[4]) - result - end -.,., +# reduce 6 omitted -# reduce 8 omitted - -module_eval(<<'.,.,', 'grammar.ry', 35) - def _reduce_9(val, _values, result) - val[1].each {|p,v| - val[0][p] = v - } - result = val[0] +module_eval(<<'.,.,', 'grammar.ry', 31) + def _reduce_7(val, _values, result) + val[1].each {|p,v| + val[0][p] = v + } + result = val[0] + result end .,., -module_eval(<<'.,.,', 'grammar.ry', 42) - def _reduce_10(val, _values, result) - result = {val[0] => val[1]} +module_eval(<<'.,.,', 'grammar.ry', 38) + def _reduce_8(val, _values, result) + result = {val[0] => val[1]} result end .,., -# reduce 11 omitted - -# reduce 12 omitted - -# reduce 13 omitted +# reduce 9 omitted -# reduce 14 omitted +# reduce 10 omitted def _reduce_none(val, _values, result) val[0] diff --git a/lib/puppet/external/pson/pure/parser.rb b/lib/puppet/external/pson/pure/parser.rb index f14e6161c..bcdaf7b64 100644 --- a/lib/puppet/external/pson/pure/parser.rb +++ b/lib/puppet/external/pson/pure/parser.rb @@ -118,7 +118,7 @@ module PSON else raise TypeError, "#{source.inspect} is not like a string" end - if defined?(::Encoding) + if supports_encodings?(source) if source.encoding == ::Encoding::ASCII_8BIT b = source[0, 4].bytes.to_a source = @@ -157,6 +157,14 @@ module PSON source end + def supports_encodings?(string) + # Some modules, such as REXML on 1.8.7 (see #22804) can actually create + # a top-level Encoding constant when they are misused. Therefore + # checking for just that constant is not enough, so we'll be a bit more + # robust about if we can actually support encoding transformations. + string.respond_to?(:encoding) && defined?(::Encoding) + end + # Unescape characters in strings. UNESCAPE_MAP = Hash.new { |h, k| h[k] = k.chr } diff --git a/lib/puppet/face/file/store.rb b/lib/puppet/face/file/store.rb index 6b145b8db..76ebbc15b 100644 --- a/lib/puppet/face/file/store.rb +++ b/lib/puppet/face/file/store.rb @@ -11,7 +11,7 @@ Puppet::Face.define(:file, '0.0.1') do EOT when_invoked do |path, options| - file = Puppet::FileBucket::File.new(IO.binread(path)) + file = Puppet::FileBucket::File.new(Puppet::FileSystem::File.new(path).binread) Puppet::FileBucket::File.indirection.terminus_class = :file Puppet::FileBucket::File.indirection.save file diff --git a/lib/puppet/face/module/generate.rb b/lib/puppet/face/module/generate.rb index a30a2ff59..fc9ca5f74 100644 --- a/lib/puppet/face/module/generate.rb +++ b/lib/puppet/face/module/generate.rb @@ -17,16 +17,14 @@ Puppet::Face.define(:module, '1.0.0') do $ puppet module generate puppetlabs-ssh notice: Generating module at /Users/kelseyhightower/puppetlabs-ssh puppetlabs-ssh - puppetlabs-ssh/tests - puppetlabs-ssh/tests/init.pp - puppetlabs-ssh/spec - puppetlabs-ssh/spec/spec_helper.rb - puppetlabs-ssh/spec/spec.opts - puppetlabs-ssh/README puppetlabs-ssh/Modulefile - puppetlabs-ssh/metadata.json + puppetlabs-ssh/README puppetlabs-ssh/manifests puppetlabs-ssh/manifests/init.pp + puppetlabs-ssh/spec + puppetlabs-ssh/spec/spec_helper.rb + puppetlabs-ssh/tests + puppetlabs-ssh/tests/init.pp EOT arguments "<name>" diff --git a/lib/puppet/face/parser.rb b/lib/puppet/face/parser.rb index 3242faca7..e42919091 100644 --- a/lib/puppet/face/parser.rb +++ b/lib/puppet/face/parser.rb @@ -35,17 +35,27 @@ Puppet::Face.define(:parser, '0.0.1') do if files.empty? if not STDIN.tty? Puppet[:code] = STDIN.read - Puppet::Node::Environment.new(Puppet[:environment]).known_resource_types.clear + validate_manifest else files << Puppet[:manifest] Puppet.notice "No manifest specified. Validating the default manifest #{Puppet[:manifest]}" end end + missing_files = [] files.each do |file| + missing_files << file if ! Puppet::FileSystem::File.exist?(file) Puppet[:manifest] = file - Puppet::Node::Environment.new(Puppet[:environment]).known_resource_types.clear + validate_manifest end + raise Puppet::Error, "One or more file(s) specified did not exist:\n#{missing_files.collect {|f| " " * 3 + f + "\n"}}" if ! missing_files.empty? nil end end + + def validate_manifest + Puppet::Node::Environment.new(Puppet[:environment]).known_resource_types.clear + rescue => detail + Puppet.log_exception(detail) + exit(1) + end end diff --git a/lib/puppet/face/plugin.rb b/lib/puppet/face/plugin.rb index 76c79f38d..13ea32aa9 100644 --- a/lib/puppet/face/plugin.rb +++ b/lib/puppet/face/plugin.rb @@ -42,6 +42,12 @@ Puppet::Face.define(:plugin, '0.0.1') do Puppet[:plugindest], Puppet[:pluginsource], Puppet[:pluginsignore]).evaluate + if Puppet.features.external_facts? + Puppet::Configurer::Downloader.new("pluginfacts", + Puppet[:pluginfactdest], + Puppet[:pluginfactsource], + Puppet[:pluginsignore]).evaluate + end end when_rendering :console do |value| diff --git a/lib/puppet/feature/base.rb b/lib/puppet/feature/base.rb index dcf454298..e30aed313 100644 --- a/lib/puppet/feature/base.rb +++ b/lib/puppet/feature/base.rb @@ -70,3 +70,19 @@ Puppet.features.add(:sqlite, :libs => ["sqlite3"]) Puppet.features.add(:hiera, :libs => ["hiera"]) Puppet.features.add(:minitar, :libs => ["archive/tar/minitar"]) + +# We can manage symlinks +Puppet.features.add(:manages_symlinks) do + if ! Puppet::Util::Platform.windows? + true + else + begin + require 'Win32API' + Win32API.new('kernel32', 'CreateSymbolicLink', 'SSL', 'B') + true + rescue LoadError => err + Puppet.debug("CreateSymbolicLink is not available") + false + end + end +end diff --git a/lib/puppet/feature/external_facts.rb b/lib/puppet/feature/external_facts.rb new file mode 100644 index 000000000..1e2ad575e --- /dev/null +++ b/lib/puppet/feature/external_facts.rb @@ -0,0 +1,5 @@ +require 'facter/util/config' + +Puppet.features.add(:external_facts) { + Facter::Util::Config.respond_to?(:external_facts_dirs=) +} diff --git a/lib/puppet/feature/libuser.rb b/lib/puppet/feature/libuser.rb index bf34e20da..29a745de4 100644 --- a/lib/puppet/feature/libuser.rb +++ b/lib/puppet/feature/libuser.rb @@ -4,5 +4,5 @@ require 'puppet/util/libuser' Puppet.features.add(:libuser) { File.executable?("/usr/sbin/lgroupadd") and File.executable?("/usr/sbin/luseradd") and - File.exists?(Puppet::Util::Libuser.getconf) + Puppet::FileSystem::File.exist?(Puppet::Util::Libuser.getconf) } diff --git a/lib/puppet/feature/msgpack.rb b/lib/puppet/feature/msgpack.rb new file mode 100644 index 000000000..8c7b7f963 --- /dev/null +++ b/lib/puppet/feature/msgpack.rb @@ -0,0 +1 @@ +Puppet.features.add(:msgpack, :libs => ["msgpack"]) diff --git a/lib/puppet/feature/rails.rb b/lib/puppet/feature/rails.rb index 9e2ce4fe6..c1537effb 100644 --- a/lib/puppet/feature/rails.rb +++ b/lib/puppet/feature/rails.rb @@ -24,11 +24,11 @@ Puppet.features.add(:rails) do require 'active_record' require 'active_record/version' rescue LoadError - if FileTest.exists?("/usr/share/rails") + if Puppet::FileSystem::File.exist?("/usr/share/rails") count = 0 Dir.entries("/usr/share/rails").each do |dir| libdir = File.join("/usr/share/rails", dir, "lib") - if FileTest.exists?(libdir) and ! $LOAD_PATH.include?(libdir) + if Puppet::FileSystem::File.exist?(libdir) and ! $LOAD_PATH.include?(libdir) count += 1 $LOAD_PATH << libdir end diff --git a/lib/puppet/file_bucket/dipper.rb b/lib/puppet/file_bucket/dipper.rb index c675f09a4..5d4d9382a 100644 --- a/lib/puppet/file_bucket/dipper.rb +++ b/lib/puppet/file_bucket/dipper.rb @@ -31,8 +31,9 @@ class Puppet::FileBucket::Dipper # Back up a file to our bucket def backup(file) - raise(ArgumentError, "File #{file} does not exist") unless ::File.exist?(file) - contents = IO.binread(file) + file_handle = Puppet::FileSystem::File.new(file) + raise(ArgumentError, "File #{file} does not exist") unless file_handle.exist? + contents = file_handle.binread begin file_bucket_file = Puppet::FileBucket::File.new(contents, :bucket_path => @local_path) files_original_path = absolutize_path(file) @@ -65,8 +66,9 @@ class Puppet::FileBucket::Dipper # Restore the file def restore(file,sum) restore = true - if FileTest.exists?(file) - cursum = Digest::MD5.hexdigest(IO.binread(file)) + file_handle = Puppet::FileSystem::File.new(file) + if file_handle.exist? + cursum = Digest::MD5.hexdigest(file_handle.binread) # if the checksum has changed... # this might be extra effort @@ -79,8 +81,8 @@ class Puppet::FileBucket::Dipper if newcontents = getfile(sum) newsum = Digest::MD5.hexdigest(newcontents) changed = nil - if FileTest.exists?(file) and ! FileTest.writable?(file) - changed = ::File.stat(file).mode + if file_handle.exist? and ! file_handle.writable? + changed = Puppet::FileSystem::File.new(file).stat.mode ::File.chmod(changed | 0200, file) end ::File.open(file, ::File::WRONLY|::File::TRUNC|::File::CREAT) { |of| diff --git a/lib/puppet/file_bucket/file.rb b/lib/puppet/file_bucket/file.rb index 15e1cd76b..50e5d4d92 100644 --- a/lib/puppet/file_bucket/file.rb +++ b/lib/puppet/file_bucket/file.rb @@ -2,6 +2,7 @@ require 'puppet/file_bucket' require 'puppet/indirector' require 'puppet/util/checksums' require 'digest/md5' +require 'stringio' class Puppet::FileBucket::File # This class handles the abstract notion of a file in a filebucket. @@ -34,6 +35,16 @@ class Puppet::FileBucket::File raise ArgumentError.new("Unknown option(s): #{options.keys.join(', ')}") unless options.empty? end + # @return [Num] The size of the contents + def size + contents.size + end + + # @return [IO] A stream that reads the contents + def stream + StringIO.new(contents) + end + def checksum_type 'md5' end @@ -60,7 +71,11 @@ class Puppet::FileBucket::File def to_pson Puppet.deprecation_warning("Serializing Puppet::FileBucket::File objects to pson is deprecated.") - { "contents" => contents }.to_pson + to_data_hash.to_pson + end + + def to_data_hash + { "contents" => contents } end # This method is deprecated, but cannot be removed for awhile, otherwise @@ -69,4 +84,5 @@ class Puppet::FileBucket::File Puppet.deprecation_warning("Deserializing Puppet::FileBucket::File objects from pson is deprecated. Upgrade to a newer version.") self.new(pson["contents"]) end + end diff --git a/lib/puppet/file_serving/base.rb b/lib/puppet/file_serving/base.rb index 93b5abee2..f04a7a453 100644 --- a/lib/puppet/file_serving/base.rb +++ b/lib/puppet/file_serving/base.rb @@ -21,11 +21,18 @@ class Puppet::FileServing::Base # Return the full path to our file. Fails if there's no path set. def full_path(dummy_argument=:work_arround_for_ruby_GC_bug) - (if relative_path.nil? or relative_path == "" or relative_path == "." - path + if relative_path.nil? or relative_path == "" or relative_path == "." + full_path = path else - File.join(path, relative_path) - end).gsub(%r{//+}, "/") + full_path = File.join(path, relative_path) + end + + if Puppet.features.microsoft_windows? + # Replace multiple slashes as long as they aren't at the beginning of a filename + full_path.gsub(%r{(./)/+}, '\1') + else + full_path.gsub(%r{//+}, '/') + end end def initialize(path, options = {}) @@ -61,17 +68,21 @@ class Puppet::FileServing::Base # Stat our file, using the appropriate link-sensitive method. def stat @stat_method ||= self.links == :manage ? :lstat : :stat - File.send(@stat_method, full_path) + Puppet::FileSystem::File.new(full_path).send(@stat_method) + end + + def to_data_hash + { + 'path' => @path, + 'relative_path' => @relative_path, + 'links' => @links + } end def to_pson_data_hash { # No 'document_type' since we don't send these bare - 'data' => { - 'path' => @path, - 'relative_path' => @relative_path, - 'links' => @links - }, + 'data' => to_data_hash, 'metadata' => { 'api_version' => 1 } diff --git a/lib/puppet/file_serving/configuration.rb b/lib/puppet/file_serving/configuration.rb index d3ed15d61..c386a7e0a 100644 --- a/lib/puppet/file_serving/configuration.rb +++ b/lib/puppet/file_serving/configuration.rb @@ -1,20 +1,16 @@ -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/file_serving/mount/pluginfacts' class Puppet::FileServing::Configuration require 'puppet/file_serving/configuration/parser' - extend MonitorMixin - def self.configuration - synchronize do - @configuration ||= new - end + @configuration ||= new end Mount = Puppet::FileServing::Mount @@ -84,13 +80,15 @@ class Puppet::FileServing::Configuration @mounts["modules"].allow('*') if @mounts["modules"].empty? @mounts["plugins"] ||= Mount::Plugins.new("plugins") @mounts["plugins"].allow('*') if @mounts["plugins"].empty? + @mounts["pluginfacts"] ||= Mount::PluginFacts.new("pluginfacts") + @mounts["pluginfacts"].allow('*') if @mounts["pluginfacts"].empty? end # Read the configuration file. def readconfig(check = true) config = Puppet[:fileserverconfig] - return unless FileTest.exists?(config) + return unless Puppet::FileSystem::File.exist?(config) @parser ||= Puppet::FileServing::Configuration::Parser.new(config) diff --git a/lib/puppet/file_serving/configuration/parser.rb b/lib/puppet/file_serving/configuration/parser.rb index 3bd9c9ebd..aa09aab50 100644 --- a/lib/puppet/file_serving/configuration/parser.rb +++ b/lib/puppet/file_serving/configuration/parser.rb @@ -7,7 +7,7 @@ class Puppet::FileServing::Configuration::Parser # Parse our configuration file. def parse - raise("File server configuration #{@file} does not exist") unless FileTest.exists?(@file) + raise("File server configuration #{@file} does not exist") unless Puppet::FileSystem::File.exist?(@file) raise("Cannot read file server configuration #{@file}") unless FileTest.readable?(@file) @mounts = {} diff --git a/lib/puppet/file_serving/content.rb b/lib/puppet/file_serving/content.rb index a6706bfcc..fc5a70ea5 100644 --- a/lib/puppet/file_serving/content.rb +++ b/lib/puppet/file_serving/content.rb @@ -34,7 +34,7 @@ class Puppet::FileServing::Content < Puppet::FileServing::Base # This stat can raise an exception, too. raise(ArgumentError, "Cannot read the contents of links unless following links") if stat.ftype == "symlink" - @content = IO.binread(full_path) + @content = Puppet::FileSystem::File.new(full_path).binread end @content end diff --git a/lib/puppet/file_serving/fileset.rb b/lib/puppet/file_serving/fileset.rb index 1360a7974..20957831f 100644 --- a/lib/puppet/file_serving/fileset.rb +++ b/lib/puppet/file_serving/fileset.rb @@ -77,7 +77,7 @@ class Puppet::FileServing::Fileset links = links.to_sym raise(ArgumentError, "Invalid :links value '#{links}'") unless [:manage, :follow].include?(links) @links = links - @stat_method = File.method(@links == :manage ? :lstat : :stat) + @stat_method = @links == :manage ? :lstat : :stat end private @@ -133,7 +133,7 @@ class Puppet::FileServing::Fileset end def directory? - stat_method.call(path).directory? + Puppet::FileSystem::File.new(path).send(stat_method).directory? rescue Errno::ENOENT, Errno::EACCES false end @@ -159,7 +159,7 @@ class Puppet::FileServing::Fileset end def valid?(path) - @stat_method.call(path) + Puppet::FileSystem::File.new(path).send(@stat_method) true rescue Errno::ENOENT, Errno::EACCES false diff --git a/lib/puppet/file_serving/metadata.rb b/lib/puppet/file_serving/metadata.rb index 877f5d258..2073c53ca 100644 --- a/lib/puppet/file_serving/metadata.rb +++ b/lib/puppet/file_serving/metadata.rb @@ -65,8 +65,8 @@ class Puppet::FileServing::Metadata < Puppet::FileServing::Base end # Retrieve the attributes for this file, relative to a base directory. - # Note that File.stat raises Errno::ENOENT if the file is absent and this - # method does not catch that exception. + # Note that Puppet::FileSystem::File.new(path).stat raises Errno::ENOENT + # if the file is absent and this method does not catch that exception. def collect real_path = full_path @@ -85,7 +85,7 @@ class Puppet::FileServing::Metadata < Puppet::FileServing::Base @checksum_type = "ctime" @checksum = ("{#{@checksum_type}}") + send("#{@checksum_type}_file", path).to_s when "link" - @destination = File.readlink(real_path) + @destination = Puppet::FileSystem::File.new(real_path).readlink @checksum = ("{#{@checksum_type}}") + send("#{@checksum_type}_file", real_path).to_s rescue nil else raise ArgumentError, "Cannot manage files of type #{stat.ftype}" @@ -106,25 +106,29 @@ class Puppet::FileServing::Metadata < Puppet::FileServing::Base super(path,data) end - PSON.register_document_type('FileMetadata',self) - def to_pson_data_hash - { - 'document_type' => 'FileMetadata', - - 'data' => super['data'].update( - { - 'owner' => owner, - 'group' => group, - 'mode' => mode, - 'checksum' => { - 'type' => checksum_type, - 'value' => checksum + def to_data_hash + super.update( + { + 'owner' => owner, + 'group' => group, + 'mode' => mode, + 'checksum' => { + 'type' => checksum_type, + 'value' => checksum }, 'type' => ftype, 'destination' => destination, - }), - 'metadata' => { + } + ) + end + + PSON.register_document_type('FileMetadata',self) + def to_pson_data_hash + { + 'document_type' => 'FileMetadata', + 'data' => to_data_hash, + 'metadata' => { 'api_version' => 1 } } diff --git a/lib/puppet/file_serving/mount/file.rb b/lib/puppet/file_serving/mount/file.rb index 7f5af7f52..535a90bde 100644 --- a/lib/puppet/file_serving/mount/file.rb +++ b/lib/puppet/file_serving/mount/file.rb @@ -22,7 +22,7 @@ class Puppet::FileServing::Mount::File < Puppet::FileServing::Mount file = ::File.join(full_path, relative_path) - if !(FileTest.exist?(file) or FileTest.symlink?(file)) + if !(Puppet::FileSystem::File.exist?(file) or Puppet::FileSystem::File.new(file).symlink?) Puppet.info("File does not exist or is not accessible: #{file}") return nil end diff --git a/lib/puppet/file_serving/mount/pluginfacts.rb b/lib/puppet/file_serving/mount/pluginfacts.rb new file mode 100644 index 000000000..5ba8f7146 --- /dev/null +++ b/lib/puppet/file_serving/mount/pluginfacts.rb @@ -0,0 +1,35 @@ +require 'puppet/file_serving/mount' + +# Find files in the modules' pluginfacts directories. +# This is a very strange mount because it merges +# many directories into one. +class Puppet::FileServing::Mount::PluginFacts < Puppet::FileServing::Mount + # Return an instance of the appropriate class. + def find(relative_path, request) + return nil unless mod = request.environment.modules.find { |mod| mod.pluginfact(relative_path) } + + path = mod.pluginfact(relative_path) + + path + end + + def search(relative_path, request) + # We currently only support one kind of search on plugins - return + # them all. + Puppet.debug("Warning: calling Plugins.search with empty module path.") if request.environment.modules.empty? + paths = request.environment.modules.find_all { |mod| mod.pluginfacts? }.collect { |mod| mod.plugin_fact_directory } + if paths.empty? + # If the modulepath is valid then we still need to return a valid root + # directory for the search, but make sure nothing inside it is + # returned. + request.options[:recurse] = false + request.environment.modulepath.empty? ? nil : request.environment.modulepath + else + paths + end + end + + def valid? + true + end +end diff --git a/lib/puppet/file_system.rb b/lib/puppet/file_system.rb index d9e5b8fd3..78e0f71db 100644 --- a/lib/puppet/file_system.rb +++ b/lib/puppet/file_system.rb @@ -1,3 +1,6 @@ module Puppet::FileSystem require 'puppet/file_system/path_pattern' + require 'puppet/file_system/file' + require 'puppet/file_system/memory_file' + require 'puppet/file_system/tempfile' end diff --git a/lib/puppet/file_system/file.rb b/lib/puppet/file_system/file.rb new file mode 100644 index 000000000..d46b6846b --- /dev/null +++ b/lib/puppet/file_system/file.rb @@ -0,0 +1,261 @@ +# An abstraction over the ruby file system operations for a single file. +# +# For the time being this is being kept private so that we can evolve it for a +# while. +# +# @api private +class Puppet::FileSystem::File + attr_reader :path + + IMPL = if RUBY_VERSION =~ /^1\.8/ + require 'puppet/file_system/file18' + Puppet::FileSystem::File18 + elsif Puppet::Util::Platform.windows? + require 'puppet/file_system/file19windows' + Puppet::FileSystem::File19Windows + else + require 'puppet/file_system/file19' + Puppet::FileSystem::File19 + end + + @remembered = {} + + def self.new(path) + if @remembered.include?(path.to_s) + @remembered[path.to_s] + else + file = IMPL.allocate + file.send(:initialize, path) + file + end + end + + # Run a block of code with a file accessible in the filesystem. + # @note This API should only be used for testing + # + # @param file [Object] an object that conforms to the Puppet::FileSystem::File interface + # @api private + def self.overlay(file, &block) + remember(file) + yield + ensure + forget(file) + end + + # Create a binding between a filename and a particular instance of a file object. + # @note This API should only be used for testing + # + # @param file [Object] an object that conforms to the Puppet::FileSystem::File interface + # @api private + def self.remember(file) + @remembered[file.path.to_s] = file + end + + # Forget a remembered file + # @note This API should only be used for testing + # + # @param file [Object] an object that conforms to the Puppet::FileSystem::File interface + # @api private + def self.forget(file) + @remembered.delete(file.path.to_s) + end + + def initialize(path) + if path.is_a?(Pathname) + @path = path + else + @path = Pathname.new(path) + end + end + + def open(mode, options, &block) + ::File.open(@path, options, mode, &block) + end + + # @return [Puppet::FileSystem::File] The directory of this file + # @api public + def dir + Puppet::FileSystem::File.new(@path.dirname) + end + + # @return [Num] The size of this file + # @api public + def size + @path.size + end + + # Allows exclusive updates to a file to be made by excluding concurrent + # access using flock. This means that if the file is on a filesystem that + # does not support flock, this method will provide no protection. + # + # While polling to aquire the lock the process will wait ever increasing + # amounts of time in order to prevent multiple processes from wasting + # resources. + # + # @param mode [Integer] The mode to apply to the file if it is created + # @param options [Integer] Extra file operation mode information to use + # (defaults to read-only mode) + # @param timeout [Integer] Number of seconds to wait for the lock (defaults to 300) + # @yield The file handle, in read-write mode + # @return [Void] + # @raise [Timeout::Error] If the timeout is exceeded while waiting to aquire the lock + # @api public + def exclusive_open(mode, options = 'r', timeout = 300, &block) + wait = 0.001 + (Kernel.rand / 1000) + written = false + while !written + ::File.open(@path, options, mode) do |rf| + if rf.flock(::File::LOCK_EX|::File::LOCK_NB) + yield rf + written = true + else + sleep wait + timeout -= wait + wait *= 2 + if timeout < 0 + raise Timeout::Error, "Timeout waiting for exclusive lock on #{@path}" + end + end + end + end + end + + def each_line(&block) + ::File.open(@path) do |f| + f.each_line do |line| + yield line + end + end + end + + # @return [String] The contents of the file + def read + @path.read + end + + # @return [String] The binary contents of the file + def binread + raise NotImplementedError + end + + # Determine if a file exists by verifying that the file can be stat'd. + # Will follow symlinks and verify that the actual target path exists. + # + # @return [Boolean] true if the named file exists. + def self.exist?(path) + return IMPL.exist?(path) if IMPL.method(:exist?) != self.method(:exist?) + File.exist?(path) + end + + # Determine if a file exists by verifying that the file can be stat'd. + # Will follow symlinks and verify that the actual target path exists. + # + # @return [Boolean] true if the path of this file is present + def exist? + self.class.exist?(@path) + end + + # Determine if a file is executable. + # + # @todo Should this take into account extensions on the windows platform? + # + # @return [Boolean] true if this file can be executed + def executable? + ::File.executable?(@path) + end + + # @return [Boolean] Whether the file is writable by the current + # process + def writable? + @path.writable? + end + + # Touches the file. On most systems this updates the mtime of the file. + def touch + ::FileUtils.touch(@path) + end + + # Create the entire path as directories + def mkpath + @path.mkpath + end + + # Creates a symbolic link dest which points to the current file. + # If dest already exists: + # + # * and is a file, will raise Errno::EEXIST + # * and is a directory, will return 0 but perform no action + # * and is a symlink referencing a file, will raise Errno::EEXIST + # * and is a symlink referencing a directory, will return 0 but perform no action + # + # With the :force option set to true, when dest already exists: + # + # * and is a file, will replace the existing file with a symlink (DANGEROUS) + # * and is a directory, will return 0 but perform no action + # * and is a symlink referencing a file, will modify the existing symlink + # * and is a symlink referencing a directory, will return 0 but perform no action + # + # @param dest [String] The path to create the new symlink at + # @param [Hash] options the options to create the symlink with + # @option options [Boolean] :force overwrite dest + # @option options [Boolean] :noop do not perform the operation + # @option options [Boolean] :verbose verbose output + # + # @raise [Errno::EEXIST] dest already exists as a file and, :force is not set + # + # @return [Integer] 0 + def symlink(dest, options = {}) + FileUtils.symlink(@path, dest, options) + end + + # @return [Boolean] true if the file is a symbolic link. + def symlink? + File.symlink?(@path) + end + + # @return [String] the name of the file referenced by the given link. + def readlink + File.readlink(@path) + end + + # Deletes the named files, returning the number of names passed as arguments. + # See also Dir::rmdir. + # + # @raise an exception on any error. + # + # @return [Integer] the number of names passed as arguments + def self.unlink(*file_names) + return IMPL.unlink(*file_names) if IMPL.method(:unlink) != self.method(:unlink) + File.unlink(*file_names) + end + + # Deletes the file. + # See also Dir::rmdir. + # + # @raise an exception on any error. + # + # @return [Integer] the number of names passed as arguments, in this case 1 + def unlink + self.class.unlink(@path) + end + + # @return [File::Stat] object for the named file. + def stat + File.stat(@path) + end + + # @return [File::Stat] Same as stat, but does not follow the last symbolic + # link. Instead, reports on the link itself. + def lstat + File.lstat(@path) + end + + # Compare the contents of this file against the contents of a stream. + # @param stream [IO] The stream to compare the contents against + # @return [Boolean] Whether the contents were the same + def compare_stream(stream) + open(0, 'rb') do |this| + FileUtils.compare_stream(this, stream) + end + end +end diff --git a/lib/puppet/file_system/file18.rb b/lib/puppet/file_system/file18.rb new file mode 100644 index 000000000..99c2d5e06 --- /dev/null +++ b/lib/puppet/file_system/file18.rb @@ -0,0 +1,5 @@ +class Puppet::FileSystem::File18 < Puppet::FileSystem::File + def binread + ::File.open(@path, 'rb') { |f| f.read } + end +end diff --git a/lib/puppet/file_system/file19.rb b/lib/puppet/file_system/file19.rb new file mode 100644 index 000000000..ca011613e --- /dev/null +++ b/lib/puppet/file_system/file19.rb @@ -0,0 +1,5 @@ +class Puppet::FileSystem::File19 < Puppet::FileSystem::File + def binread + @path.binread + end +end diff --git a/lib/puppet/file_system/file19windows.rb b/lib/puppet/file_system/file19windows.rb new file mode 100644 index 000000000..87f9915fe --- /dev/null +++ b/lib/puppet/file_system/file19windows.rb @@ -0,0 +1,113 @@ +require 'puppet/file_system/file19' +require 'puppet/util/windows' + +class Puppet::FileSystem::File19Windows < Puppet::FileSystem::File19 + + def self.exist?(path) + if ! Puppet.features.manages_symlinks? + return ::File.exist?(path) + end + + path = path.to_str if path.respond_to?(:to_str) # support WatchedFile + path = path.to_s # support String and Pathname + + begin + if Puppet::Util::Windows::File.symlink?(path) + path = Puppet::Util::Windows::File.readlink(path) + end + ! Puppet::Util::Windows::File.stat(path).nil? + rescue # generally INVALID_HANDLE_VALUE which means 'file not found' + false + end + end + + def exist? + self.class.exist?(@path) + end + + def symlink(dest, options = {}) + raise_if_symlinks_unsupported + + dest_exists = self.class.exist?(dest) # returns false on dangling symlink + dest_stat = Puppet::Util::Windows::File.stat(dest) if dest_exists + dest_symlink = Puppet::Util::Windows::File.symlink?(dest) + + # silent fail to preserve semantics of original FileUtils + return 0 if dest_exists && dest_stat.ftype == 'directory' + + if dest_exists && dest_stat.ftype == 'file' && options[:force] != true + raise(Errno::EEXIST, "#{dest} already exists and the :force option was not specified") + end + + if options[:noop] != true + ::File.delete(dest) if dest_exists # can only be file + Puppet::Util::Windows::File.symlink(@path, dest) + end + + 0 + end + + def symlink? + return false if ! Puppet.features.manages_symlinks? + Puppet::Util::Windows::File.symlink?(@path) + end + + def readlink + raise_if_symlinks_unsupported + Puppet::Util::Windows::File.readlink(@path) + end + + def self.unlink(*file_names) + if ! Puppet.features.manages_symlinks? + return ::File.unlink(*file_names) + end + + file_names.each do |file_name| + file_name = file_name.to_s # handle PathName + stat = Puppet::Util::Windows::File.stat(file_name) rescue nil + + # sigh, Ruby + Windows :( + if stat && stat.ftype == 'directory' + if Puppet::Util::Windows::File.symlink?(file_name) + Dir.rmdir(file_name) + else + raise Errno::EPERM.new(file_name) + end + else + ::File.unlink(file_name) + end + end + + file_names.length + end + + def unlink + self.class.unlink(@path) + end + + def stat + if ! Puppet.features.manages_symlinks? + return super + end + Puppet::Util::Windows::File.stat(@path) + end + + def lstat + if ! Puppet.features.manages_symlinks? + return Puppet::Util::Windows::File.stat(@path) + end + Puppet::Util::Windows::File.lstat(@path) + end + + private + def raise_if_symlinks_unsupported + if ! Puppet.features.manages_symlinks? + msg = "This version of Windows does not support symlinks. Windows Vista / 2008 or higher is required." + raise Puppet::Util::Windows::Error.new(msg) + end + + if ! Puppet::Util::Windows::Process.process_privilege_symlink? + Puppet.warning "The current user does not have the necessary permission to manage symlinks." + end + end +end diff --git a/lib/puppet/file_system/memory_file.rb b/lib/puppet/file_system/memory_file.rb new file mode 100644 index 000000000..4605a7a01 --- /dev/null +++ b/lib/puppet/file_system/memory_file.rb @@ -0,0 +1,31 @@ +# An in-memory file abstraction. Commonly used with Puppet::FileSystem::File#overlay +# @api private +class Puppet::FileSystem::MemoryFile + attr_reader :path + + def self.a_missing_file(path) + new(path, :exist? => false, :executable? => false) + end + + def self.a_regular_file_containing(path, content) + new(path, :exist? => true, :executable? => false, :content => content) + end + + def self.an_executable(path) + new(path, :exist? => true, :executable? => true) + end + + def initialize(path, options) + @path = Pathname.new(path) + @exist = options[:exist?] + @executable = options[:executable?] + @content = options[:content] + end + + def exist?; @exist; end + def executable?; @executable; end + + def each_line(&block) + StringIO.new(@content).each_line(&block) + end +end diff --git a/lib/puppet/file_system/tempfile.rb b/lib/puppet/file_system/tempfile.rb new file mode 100644 index 000000000..6766a7aec --- /dev/null +++ b/lib/puppet/file_system/tempfile.rb @@ -0,0 +1,20 @@ +require 'tempfile' + +class Puppet::FileSystem::Tempfile + + # Variation of Tempfile.open which ensures that the tempfile is closed and + # unlinked before returning + # + # @param identifier [String] additional part of generated pathname + # @yieldparam file [File] the temporary file object + # @return result of the passed block + # @api private + def self.open(identifier) + file = ::Tempfile.new(identifier) + + yield file + + ensure + file.close! + end +end diff --git a/lib/puppet/indirector/active_record.rb b/lib/puppet/indirector/active_record.rb index a9f05d683..32c4dd4dc 100644 --- a/lib/puppet/indirector/active_record.rb +++ b/lib/puppet/indirector/active_record.rb @@ -1,3 +1,4 @@ +require 'puppet/rails' require 'puppet/indirector' class Puppet::Indirector::ActiveRecord < Puppet::Indirector::Terminus diff --git a/lib/puppet/indirector/catalog/compiler.rb b/lib/puppet/indirector/catalog/compiler.rb index 9b1cc0305..b2a3c251a 100644 --- a/lib/puppet/indirector/catalog/compiler.rb +++ b/lib/puppet/indirector/catalog/compiler.rb @@ -41,6 +41,7 @@ class Puppet::Resource::Catalog::Compiler < Puppet::Indirector::Code extract_facts_from_request(request) node = node_from_request(request) + node.trusted_data = trusted_hash_from_request(request) if catalog = compile(node) return catalog @@ -51,6 +52,7 @@ class Puppet::Resource::Catalog::Compiler < Puppet::Indirector::Code end end + # filter-out a catalog to remove exported resources def filter(catalog) return catalog.filter { |r| r.virtual? } if catalog.respond_to?(:filter) @@ -70,6 +72,32 @@ class Puppet::Resource::Catalog::Compiler < Puppet::Indirector::Code private + # Produces a deeply frozen hash with trusted information + # The key :authenticated is always present in the result with one of the values + # :remote, :local, false, where :remote is authenticated via cert, :local is trusted by virtue + # of running on the same machine (not a remove request), and false is an unauthenticated remot request. + # When the trusted hash value for :authenticated == false, there is no other values set in the hash. + # + def trusted_hash_from_request(request) + if request.remote? + if request.authenticated? + trust_authenticated = 'remote'.freeze + client_cert = request.node + else + trust_authenticated = false + client_cert = nil + end + else + trust_authenticated = 'local'.freeze + # Always trust local data by picking up the available parameters. + request_node = request.options[:use_node] + client_cert = request_node ? request_node.parameters['clientcert'] : nil + end + + # TODO nil or undef for client_cert missing? + trusted_hash = { 'authenticated' => trust_authenticated, 'certname' => client_cert }.freeze + end + # Add any extra data necessary to the node. def add_node_data(node) # Merge in our server-side facts, so they can be used during compilation. diff --git a/lib/puppet/indirector/certificate_request/memory.rb b/lib/puppet/indirector/certificate_request/memory.rb new file mode 100644 index 000000000..c60a1f9f1 --- /dev/null +++ b/lib/puppet/indirector/certificate_request/memory.rb @@ -0,0 +1,6 @@ +require 'puppet/ssl/certificate_request' +require 'puppet/indirector/memory' + +class Puppet::SSL::CertificateRequest::Memory < Puppet::Indirector::Memory + desc "Store certificate requests in memory. This is used for testing puppet." +end diff --git a/lib/puppet/indirector/data_binding/hiera.rb b/lib/puppet/indirector/data_binding/hiera.rb index 5271b997a..9ff640b2e 100644 --- a/lib/puppet/indirector/data_binding/hiera.rb +++ b/lib/puppet/indirector/data_binding/hiera.rb @@ -1,6 +1,50 @@ -require 'puppet/indirector/hiera' +require 'puppet/indirector/code' +require 'hiera/scope' -class Puppet::DataBinding::Hiera < Puppet::Indirector::Hiera +class Puppet::DataBinding::Hiera < Puppet::Indirector::Code desc "Retrieve data using Hiera." + + def initialize(*args) + if ! Puppet.features.hiera? + raise "Hiera terminus not supported without hiera library" + end + super + end + + if defined?(::Psych::SyntaxError) + DataBindingExceptions = [::StandardError, ::Psych::SyntaxError] + else + DataBindingExceptions = [::StandardError] + end + + def find(request) + hiera.lookup(request.key, nil, Hiera::Scope.new(request.options[:variables]), nil, nil) + rescue *DataBindingExceptions => detail + raise Puppet::DataBinding::LookupError.new(detail.message, detail) + end + + private + + def self.hiera_config + hiera_config = Puppet.settings[:hiera_config] + config = {} + + if Puppet::FileSystem::File.exist?(hiera_config) + config = Hiera::Config.load(hiera_config) + else + Puppet.warning "Config file #{hiera_config} not found, using Hiera defaults" + end + + config[:logger] = 'puppet' + config + end + + def self.hiera + @hiera ||= Hiera.new(:config => hiera_config) + end + + def hiera + self.class.hiera + end end diff --git a/lib/puppet/indirector/direct_file_server.rb b/lib/puppet/indirector/direct_file_server.rb index 62234e360..dba4d60bd 100644 --- a/lib/puppet/indirector/direct_file_server.rb +++ b/lib/puppet/indirector/direct_file_server.rb @@ -6,14 +6,14 @@ class Puppet::Indirector::DirectFileServer < Puppet::Indirector::Terminus include Puppet::FileServing::TerminusHelper def find(request) - return nil unless FileTest.exists?(request.key) + return nil unless Puppet::FileSystem::File.exist?(request.key) instance = model.new(request.key) instance.links = request.options[:links] if request.options[:links] instance end def search(request) - return nil unless FileTest.exists?(request.key) + return nil unless Puppet::FileSystem::File.exist?(request.key) path2instances(request, request.key) end end diff --git a/lib/puppet/indirector/facts/facter.rb b/lib/puppet/indirector/facts/facter.rb index a4ee18ad3..4b44b5812 100644 --- a/lib/puppet/indirector/facts/facter.rb +++ b/lib/puppet/indirector/facts/facter.rb @@ -6,6 +6,8 @@ class Puppet::Node::Facts::Facter < Puppet::Indirector::Code between Puppet and Facter. It's only `somewhat` abstract because it always returns the local host's facts, regardless of what you attempt to find." + private + def self.reload_facter Facter.clear Facter.loadfacts @@ -24,6 +26,26 @@ class Puppet::Node::Facts::Facter < Puppet::Indirector::Code end end + def self.setup_external_facts(request) + # Add any per-module fact directories to the factpath + external_facts_dirs = [] + request.environment.modules.each do |m| + if m.has_external_facts? + Puppet.info "Loading external facts from #{m.plugin_fact_directory}" + external_facts_dirs << m.plugin_fact_directory + end + end + + # Add system external fact directory if it exists + if File.directory?(Puppet[:pluginfactdest]) + external_facts_dirs << Puppet[:pluginfactdest] + end + + # Add to facter config + Facter::Util::Config.external_facts_dirs += external_facts_dirs + + end + def self.load_facts_in_dir(dir) return unless FileTest.directory?(dir) @@ -44,12 +66,15 @@ class Puppet::Node::Facts::Facter < Puppet::Indirector::Code end end + public + def destroy(facts) raise Puppet::DevError, "You cannot destroy facts in the code store; it is only used for getting facts from Facter" end # Look a host's facts up in Facter. def find(request) + self.class.setup_external_facts(request) if Puppet.features.external_facts? self.class.reload_facter self.class.load_fact_plugins result = Puppet::Node::Facts.new(request.key, Facter.to_hash) diff --git a/lib/puppet/indirector/file_bucket_file/file.rb b/lib/puppet/indirector/file_bucket_file/file.rb index a8fd79b7e..59be12451 100644 --- a/lib/puppet/indirector/file_bucket_file/file.rb +++ b/lib/puppet/indirector/file_bucket_file/file.rb @@ -9,41 +9,40 @@ module Puppet::FileBucketFile desc "Store files in a directory set based on their checksums." - def initialize - Puppet.settings.use(:filebucket) - end - - def find( request ) - checksum, files_original_path = request_to_checksum_and_path( request ) - dir_path = path_for(request.options[:bucket_path], checksum) - file_path = ::File.join(dir_path, 'contents') - - return nil unless ::File.exists?(file_path) - return nil unless path_match(dir_path, files_original_path) - - if request.options[:diff_with] - file2_path = path_for(request.options[:bucket_path], request.options[:diff_with], 'contents') - raise "could not find diff_with #{request.options[:diff_with]}" unless ::File.exists?(file2_path) - return `diff #{file_path.inspect} #{file2_path.inspect}` + def find(request) + checksum, files_original_path = request_to_checksum_and_path(request) + contents_file = path_for(request.options[:bucket_path], checksum, 'contents') + paths_file = path_for(request.options[:bucket_path], checksum, 'paths') + + if contents_file.exist? && matches(paths_file, files_original_path) + if request.options[:diff_with] + other_contents_file = path_for(request.options[:bucket_path], request.options[:diff_with], 'contents') + raise "could not find diff_with #{request.options[:diff_with]}" unless other_contents_file.exist? + return `diff #{contents_file.path.to_s.inspect} #{other_contents_file.path.to_s.inspect}` + else + Puppet.info "FileBucket read #{checksum}" + model.new(contents_file.binread) + end else - contents = IO.binread(file_path) - Puppet.info "FileBucket read #{checksum}" - model.new(contents) + nil end end def head(request) checksum, files_original_path = request_to_checksum_and_path(request) - dir_path = path_for(request.options[:bucket_path], checksum) + contents_file = path_for(request.options[:bucket_path], checksum, 'contents') + paths_file = path_for(request.options[:bucket_path], checksum, 'paths') - ::File.exists?(::File.join(dir_path, 'contents')) and path_match(dir_path, files_original_path) + contents_file.exist? && matches(paths_file, files_original_path) end - def save( request ) + def save(request) instance = request.instance - checksum, files_original_path = request_to_checksum_and_path(request) + _, files_original_path = request_to_checksum_and_path(request) + contents_file = path_for(instance.bucket_path, instance.checksum_data, 'contents') + paths_file = path_for(instance.bucket_path, instance.checksum_data, 'paths') - save_to_disk(instance, files_original_path) + save_to_disk(instance, files_original_path, contents_file, paths_file) # don't echo the request content back to the agent model.new('') @@ -55,57 +54,46 @@ module Puppet::FileBucketFile private - def path_match(dir_path, files_original_path) + def matches(paths_file, files_original_path) + paths_file.open(0640, 'a+') do |f| + path_match(f, files_original_path) + end + end + + def path_match(file_handle, files_original_path) return true unless files_original_path # if no path was provided, it's a match - paths_path = ::File.join(dir_path, 'paths') - return false unless ::File.exists?(paths_path) - ::File.open(paths_path) do |f| - f.each_line do |line| - return true if line.chomp == files_original_path - end + file_handle.rewind + file_handle.each_line do |line| + return true if line.chomp == files_original_path end return false end - def save_to_disk( bucket_file, files_original_path ) - filename = path_for(bucket_file.bucket_path, bucket_file.checksum_data, 'contents') - dir_path = path_for(bucket_file.bucket_path, bucket_file.checksum_data) - paths_path = ::File.join(dir_path, 'paths') - - # If the file already exists, touch it. - if ::File.exist?(filename) - verify_identical_file!(bucket_file) - ::FileUtils.touch(filename) - else - # Make the directories if necessary. - unless ::File.directory?(dir_path) - Puppet::Util.withumask(0007) do - ::FileUtils.mkdir_p(dir_path) - end + def save_to_disk(bucket_file, files_original_path, contents_file, paths_file) + Puppet::Util.withumask(0007) do + unless paths_file.dir.exist? + paths_file.dir.mkpath end - Puppet.info "FileBucket adding #{bucket_file.checksum}" - - # Write the file to disk. - Puppet::Util.withumask(0007) do - ::File.open(filename, ::File::WRONLY|::File::CREAT, 0440) do |of| - of.binmode - of.print bucket_file.contents + paths_file.exclusive_open(0640, 'a+') do |f| + if contents_file.exist? + verify_identical_file!(contents_file, bucket_file) + contents_file.touch + else + contents_file.open(0440, 'wb') do |of| + of.write(bucket_file.contents) + end end - ::File.open(paths_path, ::File::WRONLY|::File::CREAT, 0640) do |of| - # path will be written below - end - end - end - unless path_match(dir_path, files_original_path) - ::File.open(paths_path, 'a') do |f| - f.puts(files_original_path) + unless path_match(f, files_original_path) + f.seek(0, IO::SEEK_END) + f.puts(files_original_path) + end end end end - def request_to_checksum_and_path( request ) + def request_to_checksum_and_path(request) checksum_type, checksum, path = request.key.split(/\//, 3) if path == '' # Treat "md5/<checksum>/" like "md5/<checksum>" path = nil @@ -121,22 +109,20 @@ module Puppet::FileBucketFile dir = ::File.join(digest[0..7].split("")) basedir = ::File.join(bucket_path, dir, digest) - return basedir unless subfile - ::File.join(basedir, subfile) + Puppet::FileSystem::File.new(subfile ? ::File.join(basedir, subfile) : basedir) end - # If conflict_check is enabled, verify that the passed text is - # the same as the text in our file. - def verify_identical_file!(bucket_file) - disk_contents = IO.binread(path_for(bucket_file.bucket_path, bucket_file.checksum_data, 'contents')) + def verify_identical_file!(contents_file, bucket_file) + if bucket_file.contents.size == contents_file.size + if contents_file.compare_stream(bucket_file.stream) + Puppet.info "FileBucket got a duplicate file #{bucket_file.checksum}" + return + end + end - # If the contents don't match, then we've found a conflict. + # If the contents or sizes don't match, then we've found a conflict. # Unlikely, but quite bad. - if disk_contents != bucket_file.contents - raise Puppet::FileBucket::BucketError, "Got passed new contents for sum #{bucket_file.checksum}" - else - Puppet.info "FileBucket got a duplicate file #{bucket_file.checksum}" - end + raise Puppet::FileBucket::BucketError, "Got passed new contents for sum #{bucket_file.checksum}" end end end diff --git a/lib/puppet/indirector/hiera.rb b/lib/puppet/indirector/hiera.rb deleted file mode 100644 index 9cceb1da2..000000000 --- a/lib/puppet/indirector/hiera.rb +++ /dev/null @@ -1,39 +0,0 @@ -require 'puppet/indirector/terminus' - -class Puppet::Indirector::Hiera < Puppet::Indirector::Terminus - def initialize(*args) - if ! Puppet.features.hiera? - raise "Hiera terminus not supported without hiera library" - end - super - end - - def find(request) - hiera.lookup(request.key, nil, request.options[:variables], nil, nil) - end - - private - - def self.hiera_config - hiera_config = Puppet.settings[:hiera_config] - config = {} - - if File.exist?(hiera_config) - config = Hiera::Config.load(hiera_config) - else - Puppet.warning "Config file #{hiera_config} not found, using Hiera defaults" - end - - config[:logger] = 'puppet' - config - end - - def self.hiera - @hiera ||= Hiera.new(:config => hiera_config) - end - - def hiera - self.class.hiera - end -end - diff --git a/lib/puppet/indirector/indirection.rb b/lib/puppet/indirector/indirection.rb index a07bddcce..0c0cb2075 100644 --- a/lib/puppet/indirector/indirection.rb +++ b/lib/puppet/indirector/indirection.rb @@ -85,7 +85,7 @@ class Puppet::Indirector::Indirection def doc text = "" - text += scrub(@doc) + "\n\n" if @doc + text << scrub(@doc) << "\n\n" if @doc text << "* **Indirected Class**: `#{@indirected_class}`\n"; if terminus_setting @@ -180,6 +180,10 @@ class Puppet::Indirector::Indirection cache.save(request(:save, nil, instance, options)) end + def allow_remote_requests? + terminus.allow_remote_requests? + end + # Search for an instance in the appropriate terminus, caching the # results if caching is configured.. def find(key, options={}) diff --git a/lib/puppet/indirector/json.rb b/lib/puppet/indirector/json.rb index 1a8db8025..515ad33b6 100644 --- a/lib/puppet/indirector/json.rb +++ b/lib/puppet/indirector/json.rb @@ -21,7 +21,7 @@ class Puppet::Indirector::JSON < Puppet::Indirector::Terminus end def destroy(request) - File.unlink(path(request.key)) + Puppet::FileSystem::File.unlink(path(request.key)) rescue => detail unless detail.is_a? Errno::ENOENT raise Puppet::Error, "Could not destroy #{self.name} #{request.key}: #{detail}" diff --git a/lib/puppet/indirector/key/ca.rb b/lib/puppet/indirector/key/ca.rb index 056d037dd..d2c3482fb 100644 --- a/lib/puppet/indirector/key/ca.rb +++ b/lib/puppet/indirector/key/ca.rb @@ -9,4 +9,8 @@ class Puppet::SSL::Key::Ca < Puppet::Indirector::SslFile store_in :privatekeydir store_ca_at :cakey + + def allow_remote_requests? + false + end end diff --git a/lib/puppet/indirector/key/file.rb b/lib/puppet/indirector/key/file.rb index 1990f1a46..40f8a331d 100644 --- a/lib/puppet/indirector/key/file.rb +++ b/lib/puppet/indirector/key/file.rb @@ -7,6 +7,10 @@ class Puppet::SSL::Key::File < Puppet::Indirector::SslFile store_in :privatekeydir store_ca_at :cakey + def allow_remote_requests? + false + end + # Where should we store the public key? def public_key_path(name) if ca?(name) @@ -20,10 +24,10 @@ class Puppet::SSL::Key::File < Puppet::Indirector::SslFile def destroy(request) super - return unless FileTest.exist?(public_key_path(request.key)) + return unless Puppet::FileSystem::File.exist?(public_key_path(request.key)) begin - File.unlink(public_key_path(request.key)) + Puppet::FileSystem::File.unlink(public_key_path(request.key)) rescue => detail raise Puppet::Error, "Could not remove #{request.key} public key: #{detail}" end @@ -34,7 +38,7 @@ class Puppet::SSL::Key::File < Puppet::Indirector::SslFile super begin - Puppet.settings.writesub(:publickeydir, public_key_path(request.key)) { |f| f.print request.instance.content.public_key.to_pem } + Puppet.settings.setting(:publickeydir).open_file(public_key_path(request.key), 'w') { |f| f.print request.instance.content.public_key.to_pem } rescue => detail raise Puppet::Error, "Could not write #{request.key}: #{detail}" end diff --git a/lib/puppet/indirector/key/memory.rb b/lib/puppet/indirector/key/memory.rb new file mode 100644 index 000000000..527863e03 --- /dev/null +++ b/lib/puppet/indirector/key/memory.rb @@ -0,0 +1,6 @@ +require 'puppet/ssl/key' +require 'puppet/indirector/memory' + +class Puppet::SSL::Key::Memory < Puppet::Indirector::Memory + desc "Store keys in memory. This is used for testing puppet." +end diff --git a/lib/puppet/indirector/node/write_only_yaml.rb b/lib/puppet/indirector/node/write_only_yaml.rb index b30bdc1db..3f97e72e8 100644 --- a/lib/puppet/indirector/node/write_only_yaml.rb +++ b/lib/puppet/indirector/node/write_only_yaml.rb @@ -17,7 +17,7 @@ class Puppet::Node::WriteOnlyYaml < Puppet::Indirector::Yaml # Overridden to always return nil. This is a write only terminus. # @param [Object] request Ignored. # @return [nil] This implementation always return nil' - # @api + # @api public def find(request) nil end @@ -25,7 +25,7 @@ class Puppet::Node::WriteOnlyYaml < Puppet::Indirector::Yaml # Overridden to always return nil. This is a write only terminus. # @param [Object] request Ignored. # @return [nil] This implementation always return nil - # @api + # @api public def search(request) nil end diff --git a/lib/puppet/indirector/request.rb b/lib/puppet/indirector/request.rb index 9f95b2f2e..4eacee11b 100644 --- a/lib/puppet/indirector/request.rb +++ b/lib/puppet/indirector/request.rb @@ -37,16 +37,12 @@ class Puppet::Indirector::Request request end - def to_pson(*args) + def to_data_hash result = { - 'document_type' => 'IndirectorRequest', - 'data' => { - 'type' => indirection_name, - 'method' => method, - 'key' => key - } + 'type' => indirection_name, + 'method' => method, + 'key' => key } - data = result['data'] attributes = {} OPTION_ATTRIBUTES.each do |key| next unless value = send(key) @@ -57,10 +53,20 @@ class Puppet::Indirector::Request attributes[opt] = value end - data['attributes'] = attributes unless attributes.empty? - data['instance'] = instance if instance + result['attributes'] = attributes unless attributes.empty? + result['instance'] = instance if instance + result + end - result.to_pson(*args) + def to_pson_data_hash + { + 'document_type' => 'IndirectorRequest', + 'data' => to_data_hash, + } + end + + def to_pson(*args) + to_pson_data_hash.to_pson(*args) end # Is this an authenticated request? diff --git a/lib/puppet/indirector/resource/ral.rb b/lib/puppet/indirector/resource/ral.rb index 30c8623c5..5a366a329 100644 --- a/lib/puppet/indirector/resource/ral.rb +++ b/lib/puppet/indirector/resource/ral.rb @@ -5,6 +5,11 @@ class Puppet::Resource::Ral < Puppet::Indirector::Code desc "Manipulate resources with the resource abstraction layer. Only used internally." + def allow_remote_requests? + Puppet.deprecation_warning("Accessing resources on the network is deprecated. See http://links.puppetlabs.com/deprecate-networked-resource") + super + end + def find( request ) # find by name res = type(request).instances.find { |o| o.name == resource_name(request) } diff --git a/lib/puppet/indirector/resource/rest.rb b/lib/puppet/indirector/resource/rest.rb index 824af41d1..9992fc057 100644 --- a/lib/puppet/indirector/resource/rest.rb +++ b/lib/puppet/indirector/resource/rest.rb @@ -1,6 +1,7 @@ require 'puppet/indirector/status' require 'puppet/indirector/rest' +# @deprecated class Puppet::Resource::Rest < Puppet::Indirector::REST desc "Maniuplate resources remotely? Undocumented." diff --git a/lib/puppet/indirector/resource/store_configs.rb b/lib/puppet/indirector/resource/store_configs.rb index 45ffdbd5d..0c249d60b 100644 --- a/lib/puppet/indirector/resource/store_configs.rb +++ b/lib/puppet/indirector/resource/store_configs.rb @@ -6,4 +6,8 @@ class Puppet::Resource::StoreConfigs < Puppet::Indirector::StoreConfigs desc %q{Part of the "storeconfigs" feature. Should not be directly set by end users.} + def allow_remote_requests? + Puppet.deprecation_warning("Accessing resources on the network is deprecated. See http://links.puppetlabs.com/deprecate-networked-resource") + super + end end diff --git a/lib/puppet/indirector/rest.rb b/lib/puppet/indirector/rest.rb index 44c26e78b..d70b43bdf 100644 --- a/lib/puppet/indirector/rest.rb +++ b/lib/puppet/indirector/rest.rb @@ -55,7 +55,8 @@ class Puppet::Indirector::REST < Puppet::Indirector::Terminus end def network(request) - Puppet::Network::HTTP::Connection.new(request.server || self.class.server, request.port || self.class.port) + Puppet::Network::HttpPool.http_instance(request.server || self.class.server, + request.port || self.class.port) end def http_get(request, path, headers = nil, *args) diff --git a/lib/puppet/indirector/ssl_file.rb b/lib/puppet/indirector/ssl_file.rb index df72ab4f5..a4ca4bd77 100644 --- a/lib/puppet/indirector/ssl_file.rb +++ b/lib/puppet/indirector/ssl_file.rb @@ -70,11 +70,11 @@ class Puppet::Indirector::SslFile < Puppet::Indirector::Terminus # Remove our file. def destroy(request) path = path(request.key) - return false unless FileTest.exist?(path) + return false unless Puppet::FileSystem::File.exist?(path) Puppet.notice "Removing file #{model} #{request.key} at '#{path}'" begin - File.unlink(path) + Puppet::FileSystem::File.unlink(path) rescue => detail raise Puppet::Error, "Could not remove #{request.key}: #{detail}" end @@ -135,10 +135,10 @@ class Puppet::Indirector::SslFile < Puppet::Indirector::Terminus # which we'll be EOL'ing at some point. This method was added at 20080702 # and should be removed at some point. def rename_files_with_uppercase(file) - return file if FileTest.exist?(file) + return file if Puppet::FileSystem::File.exist?(file) dir, short = File.split(file) - return nil unless FileTest.exist?(dir) + return nil unless Puppet::FileSystem::File.exist?(dir) raise ArgumentError, "Tried to fix SSL files to a file containing uppercase" unless short.downcase == short real_file = Dir.entries(dir).reject { |f| f =~ /^\./ }.find do |other| @@ -159,12 +159,12 @@ class Puppet::Indirector::SslFile < Puppet::Indirector::Terminus # the work or opening a filehandle manually. def write(name, path) if ca?(name) and ca_location - Puppet.settings.write(self.class.ca_setting) { |f| yield f } + Puppet.settings.setting(self.class.ca_setting).open('w') { |f| yield f } elsif file_location - Puppet.settings.write(self.class.file_setting) { |f| yield f } + Puppet.settings.setting(self.class.file_setting).open('w') { |f| yield f } elsif setting = self.class.directory_setting begin - Puppet.settings.writesub(setting, path) { |f| yield f } + Puppet.settings.setting(setting).open_file(path, 'w') { |f| yield f } rescue => detail raise Puppet::Error, "Could not write #{path} to #{setting}: #{detail}" end diff --git a/lib/puppet/indirector/terminus.rb b/lib/puppet/indirector/terminus.rb index 4e74efffd..b05ac3d8c 100644 --- a/lib/puppet/indirector/terminus.rb +++ b/lib/puppet/indirector/terminus.rb @@ -140,6 +140,10 @@ class Puppet::Indirector::Terminus self.class.name end + def allow_remote_requests? + true + end + def terminus_type self.class.terminus_type end diff --git a/lib/puppet/indirector/yaml.rb b/lib/puppet/indirector/yaml.rb index 014994b9d..9c4e6f102 100644 --- a/lib/puppet/indirector/yaml.rb +++ b/lib/puppet/indirector/yaml.rb @@ -6,7 +6,7 @@ class Puppet::Indirector::Yaml < Puppet::Indirector::Terminus # Read a given name's file in and convert it from YAML. def find(request) file = path(request.key) - return nil unless FileTest.exist?(file) + return nil unless Puppet::FileSystem::File.exist?(file) begin return Puppet::Util::Yaml.load_file(file) @@ -24,7 +24,7 @@ class Puppet::Indirector::Yaml < Puppet::Indirector::Terminus basedir = File.dirname(file) # This is quite likely a bad idea, since we're not managing ownership or modes. - Dir.mkdir(basedir) unless FileTest.exist?(basedir) + Dir.mkdir(basedir) unless Puppet::FileSystem::File.exist?(basedir) begin Puppet::Util::Yaml.dump(request.instance, file) @@ -46,7 +46,7 @@ class Puppet::Indirector::Yaml < Puppet::Indirector::Terminus def destroy(request) file_path = path(request.key) - File.unlink(file_path) if File.exists?(file_path) + Puppet::FileSystem::File.unlink(file_path) if Puppet::FileSystem::File.exist?(file_path) end def search(request) diff --git a/lib/puppet/interface/documentation.rb b/lib/puppet/interface/documentation.rb index ec312512e..d90a68a62 100644 --- a/lib/puppet/interface/documentation.rb +++ b/lib/puppet/interface/documentation.rb @@ -1,19 +1,12 @@ class Puppet::Interface # @api private module DocGen + require 'puppet/util/docs' + # @api private def self.strip_whitespace(text) - text.gsub!(/[ \t\f]+$/, '') - - # We need to identify an indent: the minimum number of whitespace - # characters at the start of any line in the text. - indent = text.split(/\n/).map {|x| x.index(/[^\s]/) }.compact.min - - if indent > 0 then - text.gsub!(/^[ \t\f]{0,#{indent}}/, '') - end - - return text + # I don't want no... + Puppet::Util::Docs.scrub(text) end # The documentation attributes all have some common behaviours; previously diff --git a/lib/puppet/module.rb b/lib/puppet/module.rb index 8d5434edd..39274c044 100644 --- a/lib/puppet/module.rb +++ b/lib/puppet/module.rb @@ -20,6 +20,7 @@ class Puppet::Module "files" => "files", "templates" => "templates", "plugins" => "lib", + "pluginfacts" => "facts.d", } # Find and return the +module+ that +path+ belongs to. If +path+ is @@ -53,10 +54,14 @@ class Puppet::Module def has_metadata? return false unless metadata_file - return false unless FileTest.exist?(metadata_file) - - metadata = PSON.parse File.read(metadata_file) + return false unless Puppet::FileSystem::File.exist?(metadata_file) + begin + metadata = PSON.parse(File.read(metadata_file)) + rescue PSON::PSONError => e + Puppet.debug("#{name} has an invalid and unparsable metadata.json file. The parse error: #{e.message}") + return false + end return metadata.is_a?(Hash) && !metadata.keys.empty? end @@ -66,7 +71,7 @@ class Puppet::Module # we have files of a given type. define_method(type +'?') do type_subpath = subpath(location) - unless FileTest.exist?(type_subpath) + unless Puppet::FileSystem::File.exist?(type_subpath) Puppet.debug("No #{type} found in subpath '#{type_subpath}' " + "(file / directory does not exist)") return false @@ -89,7 +94,7 @@ class Puppet::Module full_path = subpath(location) end - return nil unless FileTest.exist?(full_path) + return nil unless Puppet::FileSystem::File.exist?(full_path) return full_path end @@ -148,7 +153,7 @@ class Puppet::Module end def all_manifests - return [] unless File.exists?(manifests) + return [] unless Puppet::FileSystem::File.exist?(manifests) Dir.glob(File.join(manifests, '**', '*.{rb,pp}')) end @@ -169,6 +174,14 @@ class Puppet::Module subpath("lib") end + def plugin_fact_directory + subpath("facts.d") + end + + def has_external_facts? + File.directory?(plugin_fact_directory) + end + def supports(name, version = nil) @supports ||= [] @supports << [name, version] diff --git a/lib/puppet/module_tool/applications/builder.rb b/lib/puppet/module_tool/applications/builder.rb index d454a7bd4..65923c1d8 100644 --- a/lib/puppet/module_tool/applications/builder.rb +++ b/lib/puppet/module_tool/applications/builder.rb @@ -60,7 +60,7 @@ module Puppet::ModuleTool when *Puppet::ModuleTool::ARTIFACTS next else - FileUtils.cp_r path, build_path + FileUtils.cp_r path, build_path, :preserve => true end end end diff --git a/lib/puppet/module_tool/applications/installer.rb b/lib/puppet/module_tool/applications/installer.rb index e53f6308b..772d7a831 100644 --- a/lib/puppet/module_tool/applications/installer.rb +++ b/lib/puppet/module_tool/applications/installer.rb @@ -32,7 +32,7 @@ module Puppet::ModuleTool if is_module_package?(@name) @source = :filesystem @filename = File.expand_path(@name) - raise MissingPackageError, :requested_package => @filename unless File.exist?(@filename) + raise MissingPackageError, :requested_package => @filename unless Puppet::FileSystem::File.exist?(@filename) parsed = parse_filename(@filename) @module_name = parsed[:module_name] diff --git a/lib/puppet/module_tool/checksums.rb b/lib/puppet/module_tool/checksums.rb index 0985b71e4..044357a8f 100644 --- a/lib/puppet/module_tool/checksums.rb +++ b/lib/puppet/module_tool/checksums.rb @@ -16,7 +16,7 @@ module Puppet::ModuleTool # Return checksum for the +Pathname+. def checksum(pathname) - return Digest::MD5.hexdigest(IO.binread(pathname)) + return Digest::MD5.hexdigest(Puppet::FileSystem::File.new(pathname).binread) end # Return checksums for object's +Pathname+, generate if it's needed. diff --git a/lib/puppet/module_tool/dependency.rb b/lib/puppet/module_tool/dependency.rb index 847a2e3c1..222e714c8 100644 --- a/lib/puppet/module_tool/dependency.rb +++ b/lib/puppet/module_tool/dependency.rb @@ -15,12 +15,16 @@ module Puppet::ModuleTool @repository = repository ? Puppet::Forge::Repository.new(repository) : nil end - # Return PSON representation of this data. - def to_pson(*args) + def to_data_hash result = { :name => @full_module_name } result[:version_requirement] = @version_requirement if @version_requirement && ! @version_requirement.nil? result[:repository] = @repository.to_s if @repository && ! @repository.nil? - result.to_pson(*args) + result + end + + # Return PSON representation of this data. + def to_pson(*args) + to_data_hash.to_pson(*args) end end end diff --git a/lib/puppet/module_tool/metadata.rb b/lib/puppet/module_tool/metadata.rb index 37a753626..650043802 100644 --- a/lib/puppet/module_tool/metadata.rb +++ b/lib/puppet/module_tool/metadata.rb @@ -129,7 +129,7 @@ module Puppet::ModuleTool end end - def to_hash() + def to_data_hash() return extra_metadata.merge({ 'name' => @full_module_name, 'version' => @version, @@ -145,9 +145,13 @@ module Puppet::ModuleTool }) end + def to_hash() + to_data_hash + end + # Return the PSON record representing this instance. def to_pson(*args) - return to_hash.to_pson(*args) + return to_data_hash.to_pson(*args) end end end diff --git a/lib/puppet/module_tool/tar.rb b/lib/puppet/module_tool/tar.rb index 4f9f87ed2..6b3257cf4 100644 --- a/lib/puppet/module_tool/tar.rb +++ b/lib/puppet/module_tool/tar.rb @@ -4,7 +4,8 @@ module Puppet::ModuleTool::Tar require 'puppet/module_tool/tar/mini' def self.instance(module_name) - if Facter.value('osfamily') == 'Solaris' && Puppet::Util.which('gtar') && ! Puppet::Util::Platform.windows? + gtar_platforms = ['Solaris', 'OpenBSD'] + if gtar_platforms.include?(Facter.value('osfamily')) && Puppet::Util.which('gtar') Solaris.new elsif Puppet::Util.which('tar') && ! Puppet::Util::Platform.windows? Gnu.new diff --git a/lib/puppet/module_tool/tar/gnu.rb b/lib/puppet/module_tool/tar/gnu.rb index 0e663b7fa..d8fc3378f 100644 --- a/lib/puppet/module_tool/tar/gnu.rb +++ b/lib/puppet/module_tool/tar/gnu.rb @@ -1,8 +1,12 @@ class Puppet::ModuleTool::Tar::Gnu + def initialize(command = "tar") + @command = command + end + def unpack(sourcefile, destdir, owner) - Puppet::Util::Execution.execute("tar xzf #{sourcefile} --no-same-permissions --no-same-owner -C #{destdir}") + Puppet::Util::Execution.execute("#{@command} xzf #{sourcefile} --no-same-owner -C #{destdir}") Puppet::Util::Execution.execute("find #{destdir} -type d -exec chmod 755 {} +") - Puppet::Util::Execution.execute("find #{destdir} -type f -exec chmod 644 {} +") + Puppet::Util::Execution.execute("find #{destdir} -type f -exec chmod a-wst {} +") Puppet::Util::Execution.execute("chown -R #{owner} #{destdir}") end diff --git a/lib/puppet/module_tool/tar/mini.rb b/lib/puppet/module_tool/tar/mini.rb index 8288876ae..f64577ea0 100644 --- a/lib/puppet/module_tool/tar/mini.rb +++ b/lib/puppet/module_tool/tar/mini.rb @@ -7,6 +7,8 @@ class Puppet::ModuleTool::Tar::Mini Zlib::GzipReader.open(sourcefile) do |reader| Archive::Tar::Minitar.unpack(reader, destdir) do |action, name, stats| case action + when :file_done + File.chmod(0444, "#{destdir}/#{name}") when :dir, :file_start validate_entry(destdir, name) Puppet.debug("extracting #{destdir}/#{name}") diff --git a/lib/puppet/module_tool/tar/solaris.rb b/lib/puppet/module_tool/tar/solaris.rb index c618fbf67..fb3e58bbf 100644 --- a/lib/puppet/module_tool/tar/solaris.rb +++ b/lib/puppet/module_tool/tar/solaris.rb @@ -1,8 +1,5 @@ class Puppet::ModuleTool::Tar::Solaris < Puppet::ModuleTool::Tar::Gnu - def unpack(sourcefile, destdir, owner) - Puppet::Util::Execution.execute("gtar xzf #{sourcefile} --no-same-permissions --no-same-owner -C #{destdir}") - Puppet::Util::Execution.execute("find #{destdir} -type d -exec chmod 755 {} +") - Puppet::Util::Execution.execute("find #{destdir} -type f -exec chmod 644 {} +") - Puppet::Util::Execution.execute("chown -R #{owner} #{destdir}") + def initialize + super("gtar") end end diff --git a/lib/puppet/network/authconfig.rb b/lib/puppet/network/authconfig.rb index 1c3eaede6..527774598 100644 --- a/lib/puppet/network/authconfig.rb +++ b/lib/puppet/network/authconfig.rb @@ -3,8 +3,6 @@ require 'puppet/network/rights' module Puppet class ConfigurationError < Puppet::Error; end class Network::AuthConfig - - extend MonitorMixin attr_accessor :rights DEFAULT_ACL = [ diff --git a/lib/puppet/network/authentication.rb b/lib/puppet/network/authentication.rb index 91e16db72..f10e461d3 100644 --- a/lib/puppet/network/authentication.rb +++ b/lib/puppet/network/authentication.rb @@ -14,7 +14,7 @@ module Puppet::Network::Authentication certs << Puppet::SSL::CertificateAuthority.instance.host.certificate if Puppet::SSL::CertificateAuthority.ca? # Always check the host cert if we have one, this will be the agent or master cert depending on the run mode - certs << Puppet::SSL::Host.localhost.certificate if FileTest.exist?(Puppet[:hostcert]) + certs << Puppet::SSL::Host.localhost.certificate if Puppet::FileSystem::File.exist?(Puppet[:hostcert]) # Remove nil values for caller convenience certs.compact.each do |cert| diff --git a/lib/puppet/network/authstore.rb b/lib/puppet/network/authstore.rb index 90498002d..ba86ed90b 100755..100644 --- a/lib/puppet/network/authstore.rb +++ b/lib/puppet/network/authstore.rb @@ -82,21 +82,20 @@ module Puppet end def interpolate(match) - Thread.current[:declarations] = @declarations.collect { |ace| ace.interpolate(match) }.sort + @modified_declarations = @declarations.collect { |ace| ace.interpolate(match) }.sort end def reset_interpolation - Thread.current[:declarations] = nil + @modified_declarations = nil end private - # returns our ACEs list, but if we have a modification of it - # in our current thread, let's return it - # this is used if we want to override the this purely immutable list - # by a modified version in a multithread safe way. + # Returns our ACEs list, but if we have a modification of it, let's return + # it. This is used if we want to override the this purely immutable list + # by a modified version. def declarations - Thread.current[:declarations] || @declarations + @modified_declarations || @declarations end # Store the results of a pattern into our hash. Basically just diff --git a/lib/puppet/network/format.rb b/lib/puppet/network/format.rb index 69895c344..e50dfd32a 100644 --- a/lib/puppet/network/format.rb +++ b/lib/puppet/network/format.rb @@ -1,10 +1,9 @@ -require 'puppet/provider' -require 'puppet/provider/confiner' +require 'puppet/confiner' # A simple class for modeling encoding formats for moving # instances around the network. class Puppet::Network::Format - include Puppet::Provider::Confiner + include Puppet::Confiner attr_reader :name, :mime attr_accessor :intern_method, :render_method, :intern_multiple_method, :render_multiple_method, :weight, :required_methods, :extension diff --git a/lib/puppet/network/format_handler.rb b/lib/puppet/network/format_handler.rb index 880b58b3c..d341b4335 100644 --- a/lib/puppet/network/format_handler.rb +++ b/lib/puppet/network/format_handler.rb @@ -5,6 +5,8 @@ require 'puppet/network/format' module Puppet::Network::FormatHandler class FormatError < Puppet::Error; end + ALL_MEDIA_TYPES = '*/*'.freeze + @formats = {} def self.create(*args, &block) @@ -70,24 +72,20 @@ module Puppet::Network::FormatHandler # that generally conforms to an HTTP Accept header. Any quality specifiers # are ignored and instead the formats are simply in strict preference order # (most preferred is first) - # @param supported [Array<Symbol>] the names of the supported formats (order - # does not matter) + # @param supported [Array<Symbol>] the names of the supported formats (the + # most preferred format is first) # @return [Puppet::Network::Format, nil] the most suitable format # @api private def self.most_suitable_format_for(accepted, supported) format_name = accepted.collect do |accepted| accepted.to_s.sub(/;q=.*$/, '') end.collect do |accepted| - begin - if accepted == '*/*' - formats - else - format_to_canonical_name(accepted) - end - rescue ArgumentError - nil + if accepted == ALL_MEDIA_TYPES + supported.first + else + format_to_canonical_name_or_nil(accepted) end - end.flatten.find do |accepted| + end.find do |accepted| supported.include?(accepted) end @@ -95,6 +93,13 @@ module Puppet::Network::FormatHandler format_for(format_name) end end + + # @api private + def self.format_to_canonical_name_or_nil(format) + format_to_canonical_name(format) + rescue ArgumentError + nil + end end require 'puppet/network/formats' diff --git a/lib/puppet/network/format_support.rb b/lib/puppet/network/format_support.rb index 7cc6cc001..79f7fe665 100644 --- a/lib/puppet/network/format_support.rb +++ b/lib/puppet/network/format_support.rb @@ -1,6 +1,7 @@ require 'puppet/network/format_handler' # Provides network serialization support when included +# @api public module Puppet::Network::FormatSupport def self.included(klass) klass.extend(ClassMethods) @@ -83,6 +84,10 @@ module Puppet::Network::FormatSupport end end + def to_msgpack(*args) + to_data_hash.to_msgpack(*args) + end + def render(format = nil) format ||= self.class.default_format @@ -102,5 +107,14 @@ module Puppet::Network::FormatSupport def support_format?(name) self.class.support_format?(name) end + + # @comment Document to_data_hash here as it is called as a hook from to_msgpack if it exists + # @!method to_data_hash(*args) + # @api public + # @abstract + # This method may be implemented to return a hash object that is used for serializing. + # The object returned by this method should contain all the info needed to instantiate it again. + # If the method exists it will be called from to_msgpack and other serialization methods. + # @return [Hash] end diff --git a/lib/puppet/network/formats.rb b/lib/puppet/network/formats.rb index 4aaeddedd..62e40d376 100644 --- a/lib/puppet/network/formats.rb +++ b/lib/puppet/network/formats.rb @@ -1,5 +1,31 @@ require 'puppet/network/format_handler' +Puppet::Network::FormatHandler.create_serialized_formats(:msgpack, :weight => 20, :mime => "application/x-msgpack", :required_methods => [:render_method, :intern_method]) do + def intern(klass, text) + data = MessagePack.unpack(text) + return data if data.is_a?(klass) + klass.from_pson(data) + end + + def intern_multiple(klass, text) + MessagePack.unpack(text).collect do |data| + klass.from_pson(data) + end + end + + def render(instance) + instance.to_msgpack + end + + def render_multiple(instances) + instances.to_msgpack + end + + def supported?(klass) + Puppet.features.msgpack? && klass.method_defined?(:to_msgpack) + end +end + Puppet::Network::FormatHandler.create_serialized_formats(:yaml) do def intern(klass, text) data = YAML.load(text, :safe => true, :deserialize_symbols => true) diff --git a/lib/puppet/network/http/connection.rb b/lib/puppet/network/http/connection.rb index c75bab97c..adb166439 100644 --- a/lib/puppet/network/http/connection.rb +++ b/lib/puppet/network/http/connection.rb @@ -24,17 +24,17 @@ module Puppet::Network::HTTP OPTION_DEFAULTS = { :use_ssl => true, - :verify_peer => true, + :verify => nil, :redirect_limit => 10 } - # Creates a new HTTP client connection to `host`:`port`. + # Creates a new HTTP client connection to `host`:`port`. # @param host [String] the host to which this client will connect to # @param port [Fixnum] the port to which this client will connect to # @param options [Hash] options influencing the properties of the created connection, # the following options are recognized: # :use_ssl [Boolean] true to connect with SSL, false otherwise, defaults to true - # :verify_peer [Boolean] true to verify the peer's certificate, false otherwise, defaults to true + # :verify [#setup_connection] An object that will configure any verification to do on the connection # :redirect_limit [Fixnum] the number of allowed redirections, defaults to 10 # passing any other option in the options hash results in a Puppet::Error exception # @note the HTTP connection itself happens lazily only when {#request}, or one of the {#get}, {#post}, {#delete}, {#head} or {#put} is called @@ -48,7 +48,7 @@ module Puppet::Network::HTTP options = OPTION_DEFAULTS.merge(options) @use_ssl = options[:use_ssl] - @verify_peer = options[:verify_peer] + @verify = options[:verify] @redirect_limit = options[:redirect_limit] end @@ -128,23 +128,19 @@ module Puppet::Network::HTTP end def execute_request(method, *args) - ssl_validator = Puppet::SSL::Validator.new(:ssl_configuration => ssl_configuration) - # Perform our own validation of the SSL connection in addition to OpenSSL - ssl_validator.register_verify_callback(connection) - response = connection.send(method, *args) # Check the peer certs and warn if they're nearing expiration. - warn_if_near_expiration(*ssl_validator.peer_certs) + warn_if_near_expiration(*@verify.peer_certs) response rescue OpenSSL::SSL::SSLError => error if error.message.include? "certificate verify failed" msg = error.message - msg << ": [" + ssl_validator.verify_errors.join('; ') + "]" + msg << ": [" + @verify.verify_errors.join('; ') + "]" raise Puppet::Error, msg elsif error.message =~ /hostname (\w+ )?not match/ - leaf_ssl_cert = ssl_validator.peer_certs.last + leaf_ssl_cert = @verify.peer_certs.last valid_certnames = [leaf_ssl_cert.name, *leaf_ssl_cert.subject_alt_names].uniq msg = valid_certnames.length > 1 ? "one of #{valid_certnames.join(', ')}" : valid_certnames.first @@ -181,23 +177,7 @@ module Puppet::Network::HTTP # Use cert information from a Puppet client to set up the http object. def cert_setup - if @verify_peer and FileTest.exist?(Puppet[:hostcert]) and FileTest.exist?(ssl_configuration.ca_auth_file) - @connection.cert_store = ssl_host.ssl_store - @connection.ca_file = ssl_configuration.ca_auth_file - @connection.cert = ssl_host.certificate.content - @connection.verify_mode = OpenSSL::SSL::VERIFY_PEER - @connection.key = ssl_host.key.content - else - # We don't have the local certificates, so we don't do any verification - # or setup at this early stage. REVISIT: Shouldn't we supply the local - # certificate details if we have them? The original code didn't. - # --daniel 2012-06-03 - - # Ruby 1.8 defaulted to this, but 1.9 defaults to peer verify, - # and we almost always talk to a dedicated, not-standard CA that - # isn't trusted out of the box. This forces the expected state. - @connection.verify_mode = OpenSSL::SSL::VERIFY_NONE - end + @verify.setup_connection(@connection) end # This method largely exists for testing purposes, so that we can @@ -205,18 +185,5 @@ module Puppet::Network::HTTP def create_connection(*args) Net::HTTP.new(*args) end - - # Use the global localhost instance. - def ssl_host - Puppet::SSL::Host.localhost - end - - def ssl_configuration - @ssl_configuration ||= Puppet::SSL::Configuration.new( - Puppet[:localcacert], - :ca_chain_file => Puppet[:ssl_client_ca_chain], - :ca_auth_file => Puppet[:ssl_client_ca_auth] - ) - end end end diff --git a/lib/puppet/network/http/handler.rb b/lib/puppet/network/http/handler.rb index b8abd80e4..3b86195d0 100644 --- a/lib/puppet/network/http/handler.rb +++ b/lib/puppet/network/http/handler.rb @@ -89,15 +89,20 @@ module Puppet::Network::HTTP::Handler configure_profiler(request_headers, request_params) Puppet::Util::Profiler.profile("Processed request #{request_method} #{request_path}") do - indirection, method, key, params = uri2indirection(request_method, request_path, request_params) + indirection_name, method, key, params = uri2indirection(request_method, request_path, request_params) - check_authorization(indirection, method, key, params) + check_authorization(indirection_name, method, key, params) warn_if_near_expiration(client_cert(request)) + indirection = Puppet::Indirector::Indirection.instance(indirection_name.to_sym) + raise ArgumentError, "Could not find indirection '#{indirection_name}'" unless indirection + + if !indirection.allow_remote_requests? + raise HTTPNotFoundError, "No handler for #{indirection.name}" + end + send("do_#{method}", indirection, key, params, request, response) end - rescue SystemExit,NoMemoryError - raise rescue HTTPError => e return do_http_control_exception(response, e) rescue Exception => e @@ -129,19 +134,13 @@ module Puppet::Network::HTTP::Handler set_response(response, exception.to_s, status) end - def model(indirection_name) - raise ArgumentError, "Could not find indirection '#{indirection_name}'" unless indirection = Puppet::Indirector::Indirection.instance(indirection_name.to_sym) - indirection.model - end - # Execute our find. - def do_find(indirection_name, key, params, request, response) - model_class = model(indirection_name) - unless result = model_class.indirection.find(key, params) - raise HTTPNotFoundError, "Could not find #{indirection_name} #{key}" + def do_find(indirection, key, params, request, response) + unless result = indirection.find(key, params) + raise HTTPNotFoundError, "Could not find #{indirection.name} #{key}" end - format = accepted_response_formatter_for(model_class, request) + format = accepted_response_formatter_for(indirection.model, request) set_content_type(response, format) rendered_result = result @@ -157,9 +156,9 @@ module Puppet::Network::HTTP::Handler end # Execute our head. - def do_head(indirection_name, key, params, request, response) - unless self.model(indirection_name).indirection.head(key, params) - raise HTTPNotFoundError, "Could not find #{indirection_name} #{key}" + def do_head(indirection, key, params, request, response) + unless indirection.head(key, params) + raise HTTPNotFoundError, "Could not find #{indirection.name} #{key}" end # No need to set a response because no response is expected from a @@ -167,38 +166,35 @@ module Puppet::Network::HTTP::Handler end # Execute our search. - def do_search(indirection_name, key, params, request, response) - model = self.model(indirection_name) - result = model.indirection.search(key, params) + def do_search(indirection, key, params, request, response) + result = indirection.search(key, params) if result.nil? - raise HTTPNotFoundError, "Could not find instances in #{indirection_name} with '#{key}'" + raise HTTPNotFoundError, "Could not find instances in #{indirection.name} with '#{key}'" end - format = accepted_response_formatter_for(model, request) + format = accepted_response_formatter_for(indirection.model, request) set_content_type(response, format) - set_response(response, model.render_multiple(format, result)) + set_response(response, indirection.model.render_multiple(format, result)) end # Execute our destroy. - def do_destroy(indirection_name, key, params, request, response) - model_class = model(indirection_name) - formatter = accepted_response_formatter_or_yaml_for(model_class, request) + def do_destroy(indirection, key, params, request, response) + formatter = accepted_response_formatter_or_yaml_for(indirection.model, request) - result = model_class.indirection.destroy(key, params) + result = indirection.destroy(key, params) set_content_type(response, formatter) set_response(response, formatter.render(result)) end # Execute our save. - def do_save(indirection_name, key, params, request, response) - model_class = model(indirection_name) - formatter = accepted_response_formatter_or_yaml_for(model_class, request) - sent_object = read_body_into_model(model_class, request) + def do_save(indirection, key, params, request, response) + formatter = accepted_response_formatter_or_yaml_for(indirection.model, request) + sent_object = read_body_into_model(indirection.model, request) - result = model_class.indirection.save(sent_object, key) + result = indirection.save(sent_object, key) set_content_type(response, formatter) set_response(response, formatter.render(result)) diff --git a/lib/puppet/network/http/webrick.rb b/lib/puppet/network/http/webrick.rb index 123b493c0..869900dad 100644 --- a/lib/puppet/network/http/webrick.rb +++ b/lib/puppet/network/http/webrick.rb @@ -10,7 +10,6 @@ require 'puppet/ssl/configuration' class Puppet::Network::HTTP::WEBrick def initialize @listening = false - @mutex = Mutex.new end def listen(address, port) @@ -25,34 +24,28 @@ class Puppet::Network::HTTP::WEBrick @server.mount('/', Puppet::Network::HTTP::WEBrickREST, :this_value_is_apparently_necessary_but_unused) - @mutex.synchronize do - raise "WEBrick server is already listening" if @listening - @listening = true - @thread = Thread.new { - @server.start { |sock| - raise "Client disconnected before connection could be established" unless IO.select([sock],nil,nil,6.2) - sock.accept - @server.run(sock) - } - } - sleep 0.1 until @server.status == :Running + raise "WEBrick server is already listening" if @listening + @listening = true + @thread = Thread.new do + @server.start do |sock| + raise "Client disconnected before connection could be established" unless IO.select([sock],nil,nil,6.2) + sock.accept + @server.run(sock) + end end + sleep 0.1 until @server.status == :Running end def unlisten - @mutex.synchronize do - raise "WEBrick server is not listening" unless @listening - @server.shutdown - wait_for_shutdown - @server = nil - @listening = false - end + raise "WEBrick server is not listening" unless @listening + @server.shutdown + wait_for_shutdown + @server = nil + @listening = false end def listening? - @mutex.synchronize do - @listening - end + @listening end def wait_for_shutdown diff --git a/lib/puppet/network/http_pool.rb b/lib/puppet/network/http_pool.rb index f037f318e..97094c9ad 100644 --- a/lib/puppet/network/http_pool.rb +++ b/lib/puppet/network/http_pool.rb @@ -2,18 +2,52 @@ require 'puppet/network/http/connection' module Puppet::Network; end -# This class is basically a placeholder for managing a pool of HTTP connections; -# at present it does not actually attempt to pool them. Historically, it did -# attempt to do so, but this didn't work well based on Puppet's threading model. -# The pooling functionality has been removed, but this abstraction is still here -# because the API is used in various places and because it could be useful -# should we decide to implement pooling at some point in the future. +# This module contains the factory methods that should be used for getting a +# Puppet::Network::HTTP::Connection instance. +# +# The name "HttpPool" is a misnomer, and a leftover of history, but we would +# like to make this cache connections in the future. +# +# @api public +# module Puppet::Network::HttpPool - # Retrieve a cached http instance if caching is enabled, else return - # a new one. + # Retrieve a connection for the given host and port. + # + # @param host [String] The hostname to connect to + # @param port [Integer] The port on the host to connect to + # @param use_ssl [Boolean] Whether to use an SSL connection + # @param verify_peer [Boolean] Whether to verify the peer credentials, if possible. Verification will not take place if the CA certificate is missing. + # @return [Puppet::Network::HTTP::Connection] + # + # @api public + # def self.http_instance(host, port, use_ssl = true, verify_peer = true) - Puppet::Network::HTTP::Connection.new(host, port, :use_ssl => use_ssl, :verify_peer => verify_peer) + verifier = if verify_peer + Puppet::SSL::Validator.default_validator() + else + Puppet::SSL::Validator.no_validator() + end + + Puppet::Network::HTTP::Connection.new(host, port, + :use_ssl => use_ssl, + :verify => verifier) end + # Get an http connection that will be secured with SSL and have the + # connection verified with the given verifier + # + # @param host [String] the DNS name to connect to + # @param port [Integer] the port to connect to + # @param verifier [#setup_connection, #peer_certs, #verify_errors] An object that will setup the appropriate + # verification on a Net::HTTP instance and report any errors and the certificates used. + # @return [Puppet::Network::HTTP::Connection] + # + # @api public + # + def self.http_ssl_instance(host, port, verifier = Puppet::SSL::Validator.default_validator()) + Puppet::Network::HTTP::Connection.new(host, port, + :use_ssl => true, + :verify => verifier) + end end diff --git a/lib/puppet/network/rights.rb b/lib/puppet/network/rights.rb index f7420a90e..f7420a90e 100755..100644 --- a/lib/puppet/network/rights.rb +++ b/lib/puppet/network/rights.rb diff --git a/lib/puppet/node.rb b/lib/puppet/node.rb index b01a4665d..09b307927 100644 --- a/lib/puppet/node.rb +++ b/lib/puppet/node.rb @@ -16,7 +16,7 @@ class Puppet::Node indirects :node, :terminus_setting => :node_terminus, :doc => "Where to find node information. A node is composed of its name, its facts, and its environment." - attr_accessor :name, :classes, :source, :ipaddress, :parameters + attr_accessor :name, :classes, :source, :ipaddress, :parameters, :trusted_data attr_reader :time, :facts ::PSON.register_document_type('Node',self) @@ -31,17 +31,25 @@ class Puppet::Node node end - def to_pson(*args) + def to_data_hash result = { + 'name' => name, + 'environment' => environment.name, + } + result['classes'] = classes unless classes.empty? + result['parameters'] = parameters unless parameters.empty? + result + end + + def to_pson_data_hash(*args) + { 'document_type' => "Node", - 'data' => {} + 'data' => to_data_hash, } - result['data']['name'] = name - result['data']['classes'] = classes unless classes.empty? - result['data']['parameters'] = parameters unless parameters.empty? - result['data']['environment'] = environment.name + end - result.to_pson(*args) + def to_pson(*args) + to_pson_data_hash.to_pson(*args) end def environment @@ -84,6 +92,7 @@ class Puppet::Node # Merge the node facts with parameters from the node source. def fact_merge if @facts = Puppet::Node::Facts.indirection.find(name, :environment => environment) + @facts.sanitize merge(@facts.values) end rescue => detail @@ -143,4 +152,11 @@ class Puppet::Node end tmp.reverse end + + # Ensures the data is frozen + # + def trusted_data=(data) + Puppet.warning("Trusted node data modified for node #{name}") unless @trusted_data.nil? + @trusted_data = data.freeze + end end diff --git a/lib/puppet/node/environment.rb b/lib/puppet/node/environment.rb index 25b454638..f79b8b35b 100644 --- a/lib/puppet/node/environment.rb +++ b/lib/puppet/node/environment.rb @@ -10,20 +10,18 @@ end # Puppet::Node::Environment acts as a container for all configuration # that is expected to vary between environments. # -# ## Thread local variables +# ## Global variables # -# The Puppet::Node::Environment uses a number of `Thread.current` variables. -# Since all web servers that Puppet runs on are single threaded these -# variables are effectively global. +# The Puppet::Node::Environment uses a number of global variables. # -# ### `Thread.current[:environment]` +# ### `$environment` # -# The 'environment' thread variable represents the current environment that's +# The 'environment' global variable represents the current environment that's # being used in the compiler. # -# ### `Thread.current[:known_resource_types]` +# ### `$known_resource_types` # -# The 'known_resource_types' thread variable represents a singleton instance +# The 'known_resource_types' global variable represents a singleton instance # of the Puppet::Resource::TypeCollection class. The variable is discarded # and regenerated if it is accessed by an environment that doesn't match the # environment of the 'known_resource_types' @@ -129,7 +127,7 @@ class Puppet::Node::Environment # @return [Puppet::Node::Environment] the currently set environment if one # has been explicitly set, else it will return the '*root*' environment def self.current - Thread.current[:environment] || root + $environment || root end # Set the environment for the current thread @@ -145,7 +143,7 @@ class Puppet::Node::Environment # # @param env [Puppet::Node::Environment] def self.current=(env) - Thread.current[:environment] = new(env) + $environment = new(env) end @@ -164,7 +162,7 @@ class Puppet::Node::Environment # @api private def self.clear @seen.clear - Thread.current[:environment] = nil + $environment = nil end # @!attribute [r] name @@ -192,17 +190,16 @@ class Puppet::Node::Environment # @param name [Symbol] The environment name def initialize(name) @name = name - extend MonitorMixin end # The current global TypeCollection # # @note The environment is loosely coupled with the {Puppet::Resource::TypeCollection} # class. While there is a 1:1 relationship between an environment and a - # TypeCollection instance, there is only one TypeCollection instance available - # at any given time. It is stored in the Thread.current collection as - # 'known_resource_types'. 'known_resource_types' is accessed as an instance - # method, but is global to all environment variables. + # TypeCollection instance, there is only one TypeCollection instance + # available at any given time. It is stored in `$known_resource_types`. + # `$known_resource_types` is accessed as an instance method, but is global + # to all environment variables. # # @api public # @return [Puppet::Resource::TypeCollection] The current global TypeCollection @@ -212,14 +209,15 @@ class Puppet::Node::Environment # always just return our thread's known-resource types. Only at the start # of a compilation (after our thread var has been set to nil) or when the # environment has changed do we delve deeper. - Thread.current[:known_resource_types] = nil if (krt = Thread.current[:known_resource_types]) && krt.environment != self - Thread.current[:known_resource_types] ||= synchronize { + $known_resource_types = nil if $known_resource_types && $known_resource_types.environment != self + $known_resource_types ||= if @known_resource_types.nil? or @known_resource_types.require_reparse? @known_resource_types = Puppet::Resource::TypeCollection.new(self) @known_resource_types.import_ast(perform_initial_import, '') + @known_resource_types + else + @known_resource_types end - @known_resource_types - } end # Yields each modules' plugin directory if the plugin directory (modulename/lib) diff --git a/lib/puppet/node/facts.rb b/lib/puppet/node/facts.rb index b18a1e88d..2be4e68b9 100755..100644 --- a/lib/puppet/node/facts.rb +++ b/lib/puppet/node/facts.rb @@ -84,16 +84,33 @@ class Puppet::Node::Facts new_facts end - def to_pson(*args) + def to_data_hash result = { 'name' => name, 'values' => strip_internal, } - result['timestamp'] = timestamp if timestamp - result['expiration'] = expiration if expiration + if timestamp + if timestamp.is_a? Time + result['timestamp'] = timestamp.iso8601(9) + else + result['timestamp'] = timestamp + end + end + + if expiration + if expiration.is_a? Time + result['expiration'] = expiration.iso8601(9) + else + result['expiration'] = expiration + end + end - result.to_pson(*args) + result + end + + def to_pson(*args) + to_data_hash.to_pson(*args) end # Add internal data to the facts for storage. @@ -109,8 +126,6 @@ class Puppet::Node::Facts self.values['_timestamp'] end - private - # Strip out that internal data. def strip_internal newvals = values.dup @@ -118,6 +133,8 @@ class Puppet::Node::Facts newvals end + private + def sanitize_fact(fact) if fact.is_a? Hash then ret = {} diff --git a/lib/puppet/parameter.rb b/lib/puppet/parameter.rb index cce9fb57e..f265a99ae 100644 --- a/lib/puppet/parameter.rb +++ b/lib/puppet/parameter.rb @@ -116,10 +116,13 @@ class Puppet::Parameter @doc ||= "" unless defined?(@addeddocvals) - @doc += value_collection.doc + @doc = Puppet::Util::Docs.scrub(@doc) + if vals = value_collection.doc + @doc << "\n\n#{vals}" + end if f = self.required_features - @doc += " Requires features #{f.flatten.collect { |f| f.to_s }.join(" ")}." + @doc << "\n\nRequires features #{f.flatten.collect { |f| f.to_s }.join(" ")}." end @addeddocvals = true end @@ -567,6 +570,16 @@ class Puppet::Parameter "'#{value}'" end end + + # @comment Document post_compile_hook here as it does not exist anywhere (called from type if implemented) + # @!method post_compile() + # @since 3.4.0 + # @api public + # @abstract A subclass may implement this - it is not implemented in the Parameter class + # This method may be implemented by a parameter in order to perform actions during compilation + # after all resources have been added to the catalog. + # @see Puppet::Type#finish + # @see Puppet::Parser::Compiler#finish end require 'puppet/parameter/path' diff --git a/lib/puppet/parameter/boolean.rb b/lib/puppet/parameter/boolean.rb index 86f0c05dd..11ad80729 100644 --- a/lib/puppet/parameter/boolean.rb +++ b/lib/puppet/parameter/boolean.rb @@ -7,4 +7,9 @@ class Puppet::Parameter::Boolean < Puppet::Parameter def unsafe_munge(value) Puppet::Coercion.boolean(value) end + + def self.initvars + super + @value_collection.newvalues(*Puppet::Coercion.boolean_values) + end end diff --git a/lib/puppet/parameter/value_collection.rb b/lib/puppet/parameter/value_collection.rb index 7e2bee331..4cbd95d6a 100644 --- a/lib/puppet/parameter/value_collection.rb +++ b/lib/puppet/parameter/value_collection.rb @@ -33,17 +33,19 @@ class Puppet::Parameter::ValueCollection unless defined?(@doc) @doc = "" unless values.empty? - @doc += " Valid values are " - @doc += @strings.collect do |value| + @doc << "Valid values are " + @doc << @strings.collect do |value| if aliases = value.aliases and ! aliases.empty? "`#{value.name}` (also called `#{aliases.join(", ")}`)" else "`#{value.name}`" end - end.join(", ") + "." + end.join(", ") << ". " end - @doc += " Values can match `" + regexes.join("`, `") + "`." unless regexes.empty? + unless regexes.empty? + @doc << "Values can match `#{regexes.join("`, `")}`." + end end @doc diff --git a/lib/puppet/parser/ast/resourceparam.rb b/lib/puppet/parser/ast/resourceparam.rb index a3be0a674..04e945c52 100644 --- a/lib/puppet/parser/ast/resourceparam.rb +++ b/lib/puppet/parser/ast/resourceparam.rb @@ -11,9 +11,10 @@ class Puppet::Parser::AST # Return the parameter and the value. def evaluate(scope) + value = @value.safeevaluate(scope) return Puppet::Parser::Resource::Param.new( :name => @param, - :value => @value.safeevaluate(scope), + :value => value.nil? ? :undef : value, :source => scope.source, :line => self.line, :file => self.file, :add => self.add ) diff --git a/lib/puppet/parser/compiler.rb b/lib/puppet/parser/compiler.rb index 20f668504..2ea34c1de 100644 --- a/lib/puppet/parser/compiler.rb +++ b/lib/puppet/parser/compiler.rb @@ -17,13 +17,8 @@ class Puppet::Parser::Compiler include Puppet::Resource::TypeCollectionHelper def self.compile(node) - # We get these from the environment and only cache them in a thread - # variable for the duration of the compilation. If nothing else is using - # the thread, though, we can leave 'em hanging round with no ill effects, - # and this is safer than cleaning them at the end and assuming that will - # stick until the next entry to this function. - Thread.current[:known_resource_types] = nil - Thread.current[:env_module_directories] = nil + $known_resource_types = nil + $env_module_directories = nil # ...and we actually do the compile now we have caching ready. new(node).compile.to_resource @@ -143,8 +138,27 @@ class Puppet::Parser::Compiler end # Evaluate all of the classes specified by the node. + # Classes with parameters are evaluated as if they were declared. + # Classes without parameters or with an empty set of parameters are evaluated + # as if they were included. This means classes with an empty set of + # parameters won't conflict even if the class has already been included. def evaluate_node_classes - evaluate_classes(@node.classes, @node_scope || topscope) + if @node.classes.is_a? Hash + classes_with_params, classes_without_params = @node.classes.partition {|name,params| params and !params.empty?} + + # The results from Hash#partition are arrays of pairs rather than hashes, + # so we have to convert to the forms evaluate_classes expects (Hash, and + # Array of class names) + classes_with_params = Hash[classes_with_params] + classes_without_params.map!(&:first) + else + classes_with_params = {} + classes_without_params = @node.classes + end + + evaluate_classes(classes_without_params, @node_scope || topscope) + + evaluate_classes(classes_with_params, @node_scope || topscope) end # Evaluate each specified class in turn. If there are any classes we can't @@ -487,10 +501,12 @@ class Puppet::Parser::Compiler node.parameters.each do |param, value| @topscope[param.to_s] = value end - # These might be nil. catalog.client_version = node.parameters["clientversion"] catalog.server_version = node.parameters["serverversion"] + if Puppet[:trusted_node_data] + @topscope.set_trusted(node.trusted_data) + end end def create_settings_scope diff --git a/lib/puppet/parser/files.rb b/lib/puppet/parser/files.rb index 1d5b64966..49f36019f 100644 --- a/lib/puppet/parser/files.rb +++ b/lib/puppet/parser/files.rb @@ -41,7 +41,7 @@ module Puppet; module Parser; module Files template_paths.collect { |path| File::join(path, template) }.each do |f| - return f if FileTest.exist?(f) + return f if Puppet::FileSystem::File.exist?(f) end end diff --git a/lib/puppet/parser/functions.rb b/lib/puppet/parser/functions.rb index 7015ec4d9..360387727 100644 --- a/lib/puppet/parser/functions.rb +++ b/lib/puppet/parser/functions.rb @@ -1,6 +1,5 @@ require 'puppet/util/autoload' require 'puppet/parser/scope' -require 'monitor' # A module for managing parser functions. Each specified function # is added to a central module that then gets included into the Scope @@ -18,8 +17,8 @@ module Puppet::Parser::Functions # # @api private def self.reset - @functions = Hash.new { |h,k| h[k] = {} }.extend(MonitorMixin) - @modules = Hash.new.extend(MonitorMixin) + @functions = Hash.new { |h,k| h[k] = {} } + @modules = Hash.new # Runs a newfunction to create a function for each of the log levels Puppet::Util::Log.levels.each do |level| @@ -46,9 +45,7 @@ module Puppet::Parser::Functions if env and ! env.is_a?(Puppet::Node::Environment) env = Puppet::Node::Environment.new(env) end - @modules.synchronize { - @modules[ (env || Environment.current || Environment.root).name ] ||= Module.new - } + @modules[ (env || Environment.current || Environment.root).name ] ||= Module.new end # Create a new Puppet DSL function. @@ -170,11 +167,9 @@ module Puppet::Parser::Functions name = name.intern func = nil - @functions.synchronize do - unless func = get_function(name) - autoloader.load(name, Environment.current) - func = get_function(name) - end + unless func = get_function(name) + autoloader.load(name, Environment.current) + func = get_function(name) end if func @@ -190,14 +185,14 @@ module Puppet::Parser::Functions ret = "" merged_functions.sort { |a,b| a[0].to_s <=> b[0].to_s }.each do |name, hash| - ret += "#{name}\n#{"-" * name.to_s.length}\n" + ret << "#{name}\n#{"-" * name.to_s.length}\n" if hash[:doc] - ret += Puppet::Util::Docs.scrub(hash[:doc]) + ret << Puppet::Util::Docs.scrub(hash[:doc]) else - ret += "Undocumented.\n" + ret << "Undocumented.\n" end - ret += "\n\n- *Type*: #{hash[:type]}\n\n" + ret << "\n\n- *Type*: #{hash[:type]}\n\n" end ret @@ -229,9 +224,7 @@ module Puppet::Parser::Functions private def merged_functions - @functions.synchronize { - @functions[Environment.root].merge(@functions[Environment.current]) - } + @functions[Environment.root].merge(@functions[Environment.current]) end def get_function(name) @@ -241,9 +234,7 @@ module Puppet::Parser::Functions def add_function(name, func) name = name.intern - @functions.synchronize { - @functions[Environment.current][name] = func - } + @functions[Environment.current][name] = func end end diff --git a/lib/puppet/parser/functions/collect.rb b/lib/puppet/parser/functions/collect.rb index 5485b36af..e30a80bb1 100644 --- a/lib/puppet/parser/functions/collect.rb +++ b/lib/puppet/parser/functions/collect.rb @@ -1,44 +1,15 @@ -require 'puppet/parser/ast/lambda' - Puppet::Parser::Functions::newfunction( :collect, :type => :rvalue, :arity => 2, :doc => <<-'ENDHEREDOC') do |args| - Applies a parameterized block to each element in a sequence of entries from the first - argument and returns an array with the result of each invocation of the parameterized block. - - This function takes two mandatory arguments: the first should be an Array or a Hash, and the second - a parameterized block as produced by the puppet syntax: - - $a.collect |$x| { ... } - - When the first argument `$a` is an Array, the block is called with each entry in turn. When the first argument - is a hash the entry is an array with `[key, value]`. - - *Examples* + The 'collect' function has been renamed to 'map'. Please update your manifests. - # Turns hash into array of values - $a.collect |$x|{ $x[1] } - - # Turns hash into array of keys - $a.collect |$x| { $x[0] } - - - Since 3.2 + The collect function is reserved for future use. + - Removed as of 3.4 - requires `parser = future`. ENDHEREDOC - receiver = args[0] - pblock = args[1] - - raise ArgumentError, ("collect(): wrong argument type (#{pblock.class}; must be a parameterized block.") unless pblock.is_a? Puppet::Parser::AST::Lambda - - case receiver - when Array - when Hash - else - raise ArgumentError, ("collect(): wrong argument type (#{receiver.class}; must be an Array or a Hash.") - end - - receiver.to_a.collect {|x| pblock.call(self, x) } -end + raise NotImplementedError, + "The 'collect' function has been renamed to 'map'. Please update your manifests." +end
\ No newline at end of file diff --git a/lib/puppet/parser/functions/contain.rb b/lib/puppet/parser/functions/contain.rb new file mode 100644 index 000000000..8eb514561 --- /dev/null +++ b/lib/puppet/parser/functions/contain.rb @@ -0,0 +1,26 @@ +# Called within a class definition, establishes a containment +# relationship with another class + +Puppet::Parser::Functions::newfunction( + :contain, + :arity => -2, + :doc => "Contain one or more classes inside the current class. If any of +these classes are undeclared, they will be declared as if called with the +`include` function. Accepts a class name, an array of class names, or a +comma-separated list of class names. + +A contained class will not be applied before the containing class is +begun, and will be finished before the containing class is finished. +" +) do |classes| + scope = self + + scope.function_include(classes) + + classes.each do |class_name| + class_resource = scope.catalog.resource("Class", class_name) + if ! scope.catalog.edge?(scope.resource, class_resource) + scope.catalog.add_edge(scope.resource, class_resource) + end + end +end diff --git a/lib/puppet/parser/functions/create_resources.rb b/lib/puppet/parser/functions/create_resources.rb index 733dc4511..1c3b910b0 100644 --- a/lib/puppet/parser/functions/create_resources.rb +++ b/lib/puppet/parser/functions/create_resources.rb @@ -44,6 +44,11 @@ Puppet::Parser::Functions::newfunction(:create_resources, :arity => -3, :doc => ENDHEREDOC raise ArgumentError, ("create_resources(): wrong number of arguments (#{args.length}; must be 2 or 3)") if args.length > 3 + raise ArgumentError, ('create_resources(): second argument must be a hash') unless args[1].is_a?(Hash) + if args.length == 3 + raise ArgumentError, ('create_resources(): third argument, if provided, must be a hash') unless args[2].is_a?(Hash) + end + type, instances, defaults = args defaults ||= {} diff --git a/lib/puppet/parser/functions/extlookup.rb b/lib/puppet/parser/functions/extlookup.rb index 2a81ccc4e..293a9ea62 100644 --- a/lib/puppet/parser/functions/extlookup.rb +++ b/lib/puppet/parser/functions/extlookup.rb @@ -100,7 +100,7 @@ This is for back compatibility to interpolate variables with %. % interpolation # if we got a custom data file, put it first in the array of search files if datafile != "" - datafiles << extlookup_datadir + "/#{datafile}.csv" if File.exists?(extlookup_datadir + "/#{datafile}.csv") + datafiles << extlookup_datadir + "/#{datafile}.csv" if Puppet::FileSystem::File.exist?(extlookup_datadir + "/#{datafile}.csv") end extlookup_precedence.each do |d| @@ -111,7 +111,7 @@ This is for back compatibility to interpolate variables with %. % interpolation datafiles.each do |file| if desired.nil? - if File.exists?(file) + if Puppet::FileSystem::File.exist?(file) result = CSV.read(file).find_all do |r| r[0] == key end diff --git a/lib/puppet/parser/functions/file.rb b/lib/puppet/parser/functions/file.rb index 569266a3b..89d78f8ba 100644 --- a/lib/puppet/parser/functions/file.rb +++ b/lib/puppet/parser/functions/file.rb @@ -10,7 +10,7 @@ Puppet::Parser::Functions::newfunction( unless Puppet::Util.absolute_path?(file) raise Puppet::ParseError, "Files must be fully qualified" end - if FileTest.exists?(file) + if Puppet::FileSystem::File.exist?(file) ret = File.read(file) break end diff --git a/lib/puppet/parser/functions/reject.rb b/lib/puppet/parser/functions/filter.rb index d19471102..7894fa48f 100644 --- a/lib/puppet/parser/functions/reject.rb +++ b/lib/puppet/parser/functions/filter.rb @@ -1,47 +1,48 @@ require 'puppet/parser/ast/lambda' Puppet::Parser::Functions::newfunction( -:reject, +:filter, :type => :rvalue, :arity => 2, :doc => <<-'ENDHEREDOC') do |args| Applies a parameterized block to each element in a sequence of entries from the first - argument and returns an array with the entires for which the block did *not* evaluate to true. + argument and returns an array or hash (same type as left operand) + with the entries for which the block evaluates to true. This function takes two mandatory arguments: the first should be an Array or a Hash, and the second a parameterized block as produced by the puppet syntax: - $a.reject |$x| { ... } + $a.filter |$x| { ... } When the first argument is an Array, the block is called with each entry in turn. When the first argument - is a hash the entry is an array with `[key, value]`. + is a Hash the entry is an array with `[key, value]`. The returned filtered object is of the same type as the receiver. *Examples* - # selects all that does not end with berry - $a = ["rasberry", "blueberry", "orange"] - $a.reject |$x| { $x =~ /berry$/ } + # selects all that end with berry + $a = ["raspberry", "blueberry", "orange"] + $a.filter |$x| { $x =~ /berry$/ } - - Since 3.2 + - Since 3.4 - requires `parser = future`. ENDHEREDOC receiver = args[0] pblock = args[1] - raise ArgumentError, ("reject(): wrong argument type (#{pblock.class}; must be a parameterized block.") unless pblock.is_a? Puppet::Parser::AST::Lambda + raise ArgumentError, ("filter(): wrong argument type (#{pblock.class}; must be a parameterized block.") unless pblock.is_a? Puppet::Parser::AST::Lambda case receiver when Array - receiver.reject {|x| pblock.call(self, x) } + receiver.select {|x| pblock.call(self, x) } when Hash - result = receiver.reject {|x, y| pblock.call(self, [x, y]) } + result = receiver.select {|x, y| pblock.call(self, [x, y]) } # Ruby 1.8.7 returns Array result = Hash[result] unless result.is_a? Hash result else - raise ArgumentError, ("reject(): wrong argument type (#{receiver.class}; must be an Array or a Hash.") + raise ArgumentError, ("filter(): wrong argument type (#{receiver.class}; must be an Array or a Hash.") end end diff --git a/lib/puppet/parser/functions/foreach.rb b/lib/puppet/parser/functions/foreach.rb deleted file mode 100644 index 113e96a58..000000000 --- a/lib/puppet/parser/functions/foreach.rb +++ /dev/null @@ -1,95 +0,0 @@ -Puppet::Parser::Functions::newfunction( -:foreach, -:type => :rvalue, -:arity => 2, -:doc => <<-'ENDHEREDOC') do |args| - Applies a parameterized block to each element in a sequence of selected entries from the first - argument and returns the first argument. - - This function takes two mandatory arguments: the first should be an Array or a Hash, and the second - a parameterized block as produced by the puppet syntax: - - $a.foreach {|$x| ... } - - When the first argument is an Array, the parameterized block should define one or two block parameters. - For each application of the block, the next element from the array is selected, and it is passed to - the block if the block has one parameter. If the block has two parameters, the first is the elements - index, and the second the value. The index starts from 0. - - $a.foreach {|$index, $value| ... } - - When the first argument is a Hash, the parameterized block should define one or two parameters. - When one parameter is defined, the iteration is performed with each entry as an array of `[key, value]`, - and when two parameters are defined the iteration is performed with key and value. - - $a.foreach {|$entry| ..."key ${$entry[0]}, value ${$entry[1]}" } - $a.foreach {|$key, $value| ..."key ${key}, value ${value}" } - - - Since 3.2 - - requires `parser = future`. - ENDHEREDOC - require 'puppet/parser/ast/lambda' - - def foreach_Array(o, scope, pblock) - return nil unless pblock - - serving_size = pblock.parameter_count - if serving_size == 0 - raise ArgumentError, "Block must define at least one parameter; value." - end - if serving_size > 2 - raise ArgumentError, "Block must define at most two parameters; index, value" - end - enumerator = o.each - index = 0 - if serving_size == 1 - (o.size).times do - pblock.call(scope, enumerator.next) - end - else - (o.size).times do - pblock.call(scope, index, enumerator.next) - index = index +1 - end - end - o - end - - def foreach_Hash(o, scope, pblock) - return nil unless pblock - serving_size = pblock.parameter_count - case serving_size - when 0 - raise ArgumentError, "Block must define at least one parameter (for hash entry key)." - when 1 - when 2 - else - raise ArgumentError, "Block must define at most two parameters (for hash entry key and value)." - end - enumerator = o.each_pair - if serving_size == 1 - (o.size).times do - pblock.call(scope, enumerator.next) - end - else - (o.size).times do - pblock.call(scope, *enumerator.next) - end - end - o - end - - raise ArgumentError, ("foreach(): wrong number of arguments (#{args.length}; must be 2)") if args.length != 2 - receiver = args[0] - pblock = args[1] - raise ArgumentError, ("foreach(): wrong argument type (#{args[1].class}; must be a parameterized block.") unless pblock.is_a? Puppet::Parser::AST::Lambda - - case receiver - when Array - foreach_Array(receiver, self, pblock) - when Hash - foreach_Hash(receiver, self, pblock) - else - raise ArgumentError, ("foreach(): wrong argument type (#{args[0].class}; must be an Array or a Hash.") - end -end diff --git a/lib/puppet/parser/functions/fqdn_rand.rb b/lib/puppet/parser/functions/fqdn_rand.rb index f9bd2d23e..ffdb64e20 100644 --- a/lib/puppet/parser/functions/fqdn_rand.rb +++ b/lib/puppet/parser/functions/fqdn_rand.rb @@ -1,12 +1,20 @@ require 'digest/md5' Puppet::Parser::Functions::newfunction(:fqdn_rand, :arity => -2, :type => :rvalue, :doc => - "Generates random numbers based on the node's fqdn. Generated random values - will be a range from 0 up to and excluding n, where n is the first parameter. - The second argument specifies a number to add to the seed and is optional, for example: + "Usage: `fqdn_rand(MAX, [SEED])`. MAX is required and must be a positive + integer; SEED is optional and may be any number or string. - $random_number = fqdn_rand(30) - $random_number_seed = fqdn_rand(30,30)") do |args| + Generates a random whole number greater than or equal to 0 and less than MAX, + combining the `$fqdn` fact and the value of SEED for repeatable randomness. + (That is, each node will get a different random number from this function, but + a given node's result will be the same every time unless its hostname changes.) + + This function is usually used for spacing out runs of resource-intensive cron + tasks that run on many nodes, which could cause a thundering herd or degrade + other services if they all fire at once. Adding a SEED can be useful when you + have more than one such task and need several unrelated random numbers per + node. (For example, `fqdn_rand(30)`, `fqdn_rand(30, 'expensive job 1')`, and + `fqdn_rand(30, 'expensive job 2')` will produce totally different numbers.)") do |args| max = args.shift.to_i seed = Digest::MD5.hexdigest([self['::fqdn'],args].join(':')).hex Puppet::Util.deterministic_rand(seed,max) diff --git a/lib/puppet/parser/functions/include.rb b/lib/puppet/parser/functions/include.rb index ca8819176..ea7acd936 100644 --- a/lib/puppet/parser/functions/include.rb +++ b/lib/puppet/parser/functions/include.rb @@ -1,5 +1,22 @@ # Include the specified classes -Puppet::Parser::Functions::newfunction(:include, :arity => -2, :doc => "Evaluate one or more classes.") do |vals| +Puppet::Parser::Functions::newfunction(:include, :arity => -2, :doc => "Declares one or more classes, causing the resources in them to be +evaluated and added to the catalog. Accepts a class name, an array of class +names, or a comma-separated list of class names. + +The `include` function can be used multiple times on the same class and will +only declare a given class once. If a class declared with `include` has any +parameters, Puppet will automatically look up values for them in Hiera, using +`<class name>::<parameter name>` as the lookup key. + +Contrast this behavior with resource-like class declarations +(`class {'name': parameter => 'value',}`), which must be used in only one place +per class and can directly set parameters. You should avoid using both `include` +and resource-like declarations with the same class. + +The `include` function does not cause classes to be contained in the class +where they are declared. For that, see the `contain` function. It also +does not create a dependency relationship between the declared class and th +surrounding class; for that, see the `require` function.") do |vals| if vals.is_a?(Array) # Protect against array inside array vals = vals.flatten diff --git a/lib/puppet/parser/functions/map.rb b/lib/puppet/parser/functions/map.rb new file mode 100644 index 000000000..3c871bc85 --- /dev/null +++ b/lib/puppet/parser/functions/map.rb @@ -0,0 +1,44 @@ +require 'puppet/parser/ast/lambda' + +Puppet::Parser::Functions::newfunction( +:map, +:type => :rvalue, +:arity => 2, +:doc => <<-'ENDHEREDOC') do |args| + Applies a parameterized block to each element in a sequence of entries from the first + argument and returns an array with the result of each invocation of the parameterized block. + + This function takes two mandatory arguments: the first should be an Array or a Hash, and the second + a parameterized block as produced by the puppet syntax: + + $a.map |$x| { ... } + + When the first argument `$a` is an Array, the block is called with each entry in turn. When the first argument + is a hash the entry is an array with `[key, value]`. + + *Examples* + + # Turns hash into array of values + $a.map |$x|{ $x[1] } + + # Turns hash into array of keys + $a.map |$x| { $x[0] } + + - Since 3.4 + - requires `parser = future`. + ENDHEREDOC + + receiver = args[0] + pblock = args[1] + + raise ArgumentError, ("map(): wrong argument type (#{pblock.class}; must be a parameterized block.") unless pblock.is_a? Puppet::Parser::AST::Lambda + + case receiver + when Array + when Hash + else + raise ArgumentError, ("map(): wrong argument type (#{receiver.class}; must be an Array or a Hash.") + end + + receiver.to_a.map {|x| pblock.call(self, x) } +end diff --git a/lib/puppet/parser/functions/select.rb b/lib/puppet/parser/functions/select.rb index d5cee8e5c..659f2013c 100644 --- a/lib/puppet/parser/functions/select.rb +++ b/lib/puppet/parser/functions/select.rb @@ -1,47 +1,15 @@ -require 'puppet/parser/ast/lambda' - Puppet::Parser::Functions::newfunction( :select, :type => :rvalue, :arity => 2, :doc => <<-'ENDHEREDOC') do |args| - Applies a parameterized block to each element in a sequence of entries from the first - argument and returns an array with the entires for which the block evaluates to true. - - This function takes two mandatory arguments: the first should be an Array or a Hash, and the second - a parameterized block as produced by the puppet syntax: - - $a.select |$x| { ... } - - When the first argument is an Array, the block is called with each entry in turn. When the first argument - is a hash the entry is an array with `[key, value]`. - - The returned filtered object is of the same type as the receiver. + The 'select' function has been renamed to 'filter'. Please update your manifests. - *Examples* - - # selects all that end with berry - $a = ["raspberry", "blueberry", "orange"] - $a.select |$x| { $x =~ /berry$/ } - - - Since 3.2 + The select function is reserved for future use. + - Removed as of 3.4 - requires `parser = future`. ENDHEREDOC - receiver = args[0] - pblock = args[1] - - raise ArgumentError, ("select(): wrong argument type (#{pblock.class}; must be a parameterized block.") unless pblock.is_a? Puppet::Parser::AST::Lambda - - case receiver - when Array - receiver.select {|x| pblock.call(self, x) } - when Hash - result = receiver.select {|x, y| pblock.call(self, [x, y]) } - # Ruby 1.8.7 returns Array - result = Hash[result] unless result.is_a? Hash - result - else - raise ArgumentError, ("select(): wrong argument type (#{receiver.class}; must be an Array or a Hash.") - end -end + raise NotImplementedError, + "The 'select' function has been renamed to 'filter'. Please update your manifests." +end
\ No newline at end of file diff --git a/lib/puppet/parser/lexer.rb b/lib/puppet/parser/lexer.rb index fee38d946..65cbef92e 100644 --- a/lib/puppet/parser/lexer.rb +++ b/lib/puppet/parser/lexer.rb @@ -347,7 +347,7 @@ class Puppet::Parser::Lexer def file=(file) @file = file @line = 1 - contents = File.exists?(file) ? File.read(file) : "" + contents = Puppet::FileSystem::File.exist?(file) ? File.read(file) : "" @scanner = StringScanner.new(contents) end diff --git a/lib/puppet/parser/parser_support.rb b/lib/puppet/parser/parser_support.rb index 2fb231ff4..b67f3c752 100644 --- a/lib/puppet/parser/parser_support.rb +++ b/lib/puppet/parser/parser_support.rb @@ -82,7 +82,7 @@ class Puppet::Parser::Parser def_delegators :@lexer, :file, :string= def file=(file) - unless FileTest.exist?(file) + unless Puppet::FileSystem::File.exist?(file) unless file =~ /\.pp$/ file = file + ".pp" end diff --git a/lib/puppet/parser/resource.rb b/lib/puppet/parser/resource.rb index 6d9f996e7..0b4c6677b 100644 --- a/lib/puppet/parser/resource.rb +++ b/lib/puppet/parser/resource.rb @@ -1,12 +1,9 @@ -require 'forwardable' require 'puppet/resource' # The primary difference between this class and its # parent is that this class has rules on who can set # parameters class Puppet::Parser::Resource < Puppet::Resource - extend Forwardable - require 'puppet/parser/resource/param' require 'puppet/util/tagging' require 'puppet/parser/yaml_trimmer' @@ -18,7 +15,6 @@ class Puppet::Parser::Resource < Puppet::Resource include Puppet::Util::MethodHelper include Puppet::Util::Errors include Puppet::Util::Logging - include Puppet::Util::Tagging include Puppet::Parser::YamlTrimmer attr_accessor :source, :scope, :collector_id @@ -56,7 +52,9 @@ class Puppet::Parser::Resource < Puppet::Resource end end - def_delegator :scope, :environment + def environment + scope.environment + end # Process the stage metaparameter for a class. A containment edge # is drawn from the class to the stage. The stage for containment @@ -188,47 +186,10 @@ class Puppet::Parser::Resource < Puppet::Resource end end - - # Create a Puppet::Resource instance from this parser resource. - # We plan, at some point, on not needing to do this conversion, but - # it's sufficient for now. - def to_resource - result = Puppet::Resource.new(type, title) - - to_hash.each do |p, v| - if v.is_a?(Puppet::Resource) - v = Puppet::Resource.new(v.type, v.title) - elsif v.is_a?(Array) - # flatten resource references arrays - v = v.flatten if v.flatten.find { |av| av.is_a?(Puppet::Resource) } - v = v.collect do |av| - av = Puppet::Resource.new(av.type, av.title) if av.is_a?(Puppet::Resource) - av - end - end - - # If the value is an array with only one value, then - # convert it to a single value. This is largely so that - # the database interaction doesn't have to worry about - # whether it returns an array or a string. - result[p] = if v.is_a?(Array) and v.length == 1 - v[0] - else - v - end - end - - result.file = self.file - result.line = self.line - result.exported = self.exported - result.virtual = self.virtual - result.tag(*self.tags) - - result - end - # Convert this resource to a RAL resource. - def_delegator :to_resource, :to_ral + def to_ral + copy_as_resource.to_ral + end private diff --git a/lib/puppet/parser/scope.rb b/lib/puppet/parser/scope.rb index c249e7566..2cad0c1dd 100644 --- a/lib/puppet/parser/scope.rb +++ b/lib/puppet/parser/scope.rb @@ -116,6 +116,9 @@ class Puppet::Parser::Scope compiler.node.name end + # TODO: 19514 - this is smelly; who uses this? functions? templates? + # What about trusted facts ? Should untrusted facts be removed from facts? + # def facts compiler.node.facts end @@ -195,8 +198,6 @@ class Puppet::Parser::Scope extend_with_functions_module - @tags = [] - # The symbol table for this scope. This is where we store variables. @symtable = Ephemeral.new(nil, true) @@ -461,6 +462,8 @@ class Puppet::Parser::Scope } end + RESERVED_VARIABLE_NAMES = ['trusted'].freeze + # 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. @@ -474,6 +477,11 @@ class Puppet::Parser::Scope raise Puppet::ParseError, "Scope variable name #{name.inspect} is a #{name.class}, not a string" end + # Check for reserved variable names + if Puppet[:trusted_node_data] && !options[:privileged] && RESERVED_VARIABLE_NAMES.include?(name) + raise Puppet::ParseError, "Attempt to assign to a reserved variable name: '#{name}'" + end + table = effective_symtable options[:ephemeral] if table.bound?(name) if options[:append] @@ -494,6 +502,29 @@ class Puppet::Parser::Scope table[name] end + def set_trusted(hash) + setvar('trusted', deep_freeze(hash), :privileged => true) + end + + # Deeply freezes the given object. The object and its content must be of the types: + # Array, Hash, Numeric, Boolean, Symbol, Regexp, NilClass, or String. All other types raises an Error. + # (i.e. if they are assignable to Puppet::Pops::Types::Data type). + # + def deep_freeze(object) + case object + when Hash + object.each {|k, v| deep_freeze(k); deep_freeze(v) } + when NilClass + # do nothing + when String + object.freeze + else + raise Puppet::Error, "Unsupported data type: '#{object.class}" + end + object + end + private :deep_freeze + # Return the effective "table" for setting variables. # This method returns the first ephemeral "table" that acts as a local scope, or this # scope's symtable. If the parameter `use_ephemeral` is true, the "top most" ephemeral "table" diff --git a/lib/puppet/parser/type_loader.rb b/lib/puppet/parser/type_loader.rb index 132242f88..4ace9f90a 100644 --- a/lib/puppet/parser/type_loader.rb +++ b/lib/puppet/parser/type_loader.rb @@ -7,61 +7,6 @@ class Puppet::Parser::TypeLoader extend Forwardable include Puppet::Node::Environment::Helper - # Helper class that makes sure we don't try to import the same file - # more than once from either the same thread or different threads. - class Helper - include MonitorMixin - def initialize - super - # These hashes are indexed by filename - @state = {} # :doing or :done - @thread = {} # if :doing, thread that's doing the parsing - @cond_var = {} # if :doing, condition var that will be signaled when done. - end - - # Execute the supplied block exactly once per file, no matter how - # many threads have asked for it to run. If another thread is - # already executing it, wait for it to finish. If this thread is - # already executing it, return immediately without executing the - # block. - # - # Note: the reason for returning immediately if this thread is - # already executing the block is to handle the case of a circular - # import--when this happens, we attempt to recursively re-parse a - # file that we are already in the process of parsing. To prevent - # an infinite regress we need to simply do nothing when the - # recursive import is attempted. - def do_once(file) - need_to_execute = synchronize do - case @state[file] - when :doing - if @thread[file] != Thread.current - @cond_var[file].wait - end - false - when :done - false - else - @state[file] = :doing - @thread[file] = Thread.current - @cond_var[file] = new_cond - true - end - end - if need_to_execute - begin - yield - ensure - synchronize do - @state[file] = :done - @thread.delete(file) - @cond_var.delete(file).broadcast - end - end - end - end - end - # Import manifest files that match a given file glob pattern. # # @param pattern [String] the file glob to apply when determining which files @@ -103,7 +48,6 @@ class Puppet::Parser::TypeLoader def initialize(env) self.environment = env - @loading_helper = Helper.new end # Try to load the object with the given fully qualified name. @@ -148,11 +92,11 @@ class Puppet::Parser::TypeLoader end def load_files(modname, files) + @loaded ||= {} loaded_asts = [] - files.each do |file| - @loading_helper.do_once(file) do - loaded_asts << parse_file(file) - end + files.reject { |file| @loaded[file] }.each do |file| + loaded_asts << parse_file(file) + @loaded[file] = true end loaded_asts.collect do |ast| diff --git a/lib/puppet/pops/binder/bindings_loader.rb b/lib/puppet/pops/binder/bindings_loader.rb index 44c14adec..eca05c1e9 100644 --- a/lib/puppet/pops/binder/bindings_loader.rb +++ b/lib/puppet/pops/binder/bindings_loader.rb @@ -35,7 +35,7 @@ class Puppet::Pops::Binder::BindingsLoader def self.loadable?(basedir, name) # note, "lib" is added by the autoloader # - paths_for_name(name).find {|p| File.exists?(File.join(basedir, "lib/puppet/bindings", p)+'.rb') } + paths_for_name(name).find {|p| Puppet::FileSystem::File.exist?(File.join(basedir, "lib/puppet/bindings", p)+'.rb') } end private diff --git a/lib/puppet/pops/binder/config/binder_config.rb b/lib/puppet/pops/binder/config/binder_config.rb index aa5c45e54..a4fb7796a 100644 --- a/lib/puppet/pops/binder/config/binder_config.rb +++ b/lib/puppet/pops/binder/config/binder_config.rb @@ -72,20 +72,20 @@ module Puppet::Pops::Binder::Config rootdir = confdir if rootdir.is_a?(String) expanded_config_file = File.expand_path(File.join(rootdir, '/binder_config.yaml')) - if File.exist?(expanded_config_file) + if Puppet::FileSystem::File.exist?(expanded_config_file) @config_file = expanded_config_file end else raise ArgumentError, "No Puppet settings 'confdir', or it is not a String" end when String - unless File.exist?(@config_file) + unless Puppet::FileSystem::File.exist?(@config_file) raise ArgumentError, "Cannot find the given binder configuration file '#{@config_file}'" end else raise ArgumentError, "The setting binder_config is expected to be a String, got: #{@config_file.class.name}." end - unless @config_file.is_a?(String) && File.exist?(@config_file) + unless @config_file.is_a?(String) && Puppet::FileSystem::File.exist?(@config_file) @config_file = nil # use defaults end diff --git a/lib/puppet/pops/binder/hiera2/bindings_provider.rb b/lib/puppet/pops/binder/hiera2/bindings_provider.rb index 3cfc41aeb..d0e5d9a5e 100644 --- a/lib/puppet/pops/binder/hiera2/bindings_provider.rb +++ b/lib/puppet/pops/binder/hiera2/bindings_provider.rb @@ -35,7 +35,7 @@ module Puppet::Pops::Binder::Hiera2 precedence = [] @config.hierarchy.each do |key, value, path| - source_file = File.join(@config.module_dir, 'hiera.config.yaml') + source_file = File.join(@config.module_dir, 'hiera.yaml') category_value = @parser.evaluate_string(scope, @parser.quote(value), source_file) hierarchy[key] = { diff --git a/lib/puppet/pops/binder/scheme_handler/confdir_hiera_scheme.rb b/lib/puppet/pops/binder/scheme_handler/confdir_hiera_scheme.rb index 8be9f9018..d2ae152f7 100644 --- a/lib/puppet/pops/binder/scheme_handler/confdir_hiera_scheme.rb +++ b/lib/puppet/pops/binder/scheme_handler/confdir_hiera_scheme.rb @@ -43,7 +43,7 @@ class Puppet::Pops::Binder::SchemeHandler::ConfdirHieraScheme < Puppetx::Puppet: end def config_exist?(uri, composer) - File.exist?(File.join(composer.confdir, uri.path, 'hiera.yaml')) + Puppet::FileSystem::File.exist?(File.join(composer.confdir, uri.path, 'hiera.yaml')) end # A hiera.yaml that exists, is readable, can be loaded, and does not have version >= 2 set is ignored. diff --git a/lib/puppet/pops/binder/scheme_handler/module_hiera_scheme.rb b/lib/puppet/pops/binder/scheme_handler/module_hiera_scheme.rb index 38f57d033..0663ee2cb 100644 --- a/lib/puppet/pops/binder/scheme_handler/module_hiera_scheme.rb +++ b/lib/puppet/pops/binder/scheme_handler/module_hiera_scheme.rb @@ -36,7 +36,7 @@ class Puppet::Pops::Binder::SchemeHandler::ModuleHieraScheme < Puppetx::Puppet:: when '*' # create new URIs, one per module name that has a hiera.yaml file relative to its root composer.name_to_module.each_pair do | name, mod | - if File.exist?(File.join(mod.path, split_path[ 2..-1 ], 'hiera.yaml' )) + if Puppet::FileSystem::File.exist?(File.join(mod.path, split_path[ 2..-1 ], 'hiera.yaml' )) path_parts =["", name] + split_path[2..-1] result << URI.parse('module-hiera:'+File.join(path_parts)) end @@ -47,7 +47,7 @@ class Puppet::Pops::Binder::SchemeHandler::ModuleHieraScheme < Puppetx::Puppet:: # If uri has query that is empty, or the text 'optional' skip this uri if it does not exist if query = uri.query() if query == '' || query == 'optional' - if File.exist?(File.join(mod.path, split_path[ 2..-1 ], 'hiera.yaml' )) + if Puppet::FileSystem::File.exist?(File.join(mod.path, split_path[ 2..-1 ], 'hiera.yaml' )) result << URI.parse('module-hiera:' + uri.path) end end diff --git a/lib/puppet/pops/issues.rb b/lib/puppet/pops/issues.rb index 0276f4022..61663b167 100644 --- a/lib/puppet/pops/issues.rb +++ b/lib/puppet/pops/issues.rb @@ -195,6 +195,10 @@ module Puppet::Pops::Issues "Illegal +> operation on attribute #{name}. This operator can not be used in #{label.a_an(parent)}" end + ILLEGAL_NAME = hard_issue :ILLEGAL_NAME, :name do + "Illegal name. The given name #{name} does not conform to the naming rule \\A((::)?[a-z0-9]\w*)(::[a-z0-9]\w*)*\\z" + end + # In case a model is constructed programmatically, it must create valid type references. # ILLEGAL_CLASSREF = hard_issue :ILLEGAL_CLASSREF, :name do diff --git a/lib/puppet/pops/model/ast_transformer.rb b/lib/puppet/pops/model/ast_transformer.rb index b16d951bc..2d4504050 100644 --- a/lib/puppet/pops/model/ast_transformer.rb +++ b/lib/puppet/pops/model/ast_transformer.rb @@ -439,7 +439,10 @@ class Puppet::Pops::Model::AstTransformer args = { :code => transform(o.body) } - args[:parent] = transform(o.parent) unless is_nop?(o.parent) + args[:parent] = hostname(o.parent) unless is_nop?(o.parent) + if(args[:parent].is_a?(Array)) + raise "Illegal expression - unacceptable as a node parent" + end Puppet::Parser::AST::Node.new(hostname(o.host_matches), merge_location(args, o)) end diff --git a/lib/puppet/pops/model/model_label_provider.rb b/lib/puppet/pops/model/model_label_provider.rb index b48e7b441..469130de4 100644 --- a/lib/puppet/pops/model/model_label_provider.rb +++ b/lib/puppet/pops/model/model_label_provider.rb @@ -15,7 +15,7 @@ class Puppet::Pops::Model::ModelLabelProvider < Puppet::Pops::LabelProvider def label_Factory o ; label(o.current) end def label_Array o ; "Array Object" end def label_LiteralNumber o ; "Literal Number" end - def label_ArithmeticExpression o ; "'#{o_operator}' expression" end + def label_ArithmeticExpression o ; "'#{o.operator}' expression" end def label_AccessExpression o ; "'[]' expression" end def label_MatchExpression o ; "'#{o.operator}' expression" end def label_CollectExpression o ; label(o.query) end diff --git a/lib/puppet/pops/parser/egrammar.ra b/lib/puppet/pops/parser/egrammar.ra index eb8357d58..b45cd2cc3 100644 --- a/lib/puppet/pops/parser/egrammar.ra +++ b/lib/puppet/pops/parser/egrammar.ra @@ -14,7 +14,7 @@ token NAME SEMIC CASE DEFAULT AT LCOLLECT RCOLLECT CLASSREF token NOT OR AND UNDEF PARROW PLUS MINUS TIMES DIV LSHIFT RSHIFT UMINUS token MATCH NOMATCH REGEX IN_EDGE OUT_EDGE IN_EDGE_SUB OUT_EDGE_SUB token IN UNLESS PIPE -token LAMBDA SELBRACE +token SELBRACE token LOW prechigh @@ -203,14 +203,11 @@ call_method_with_lambda_expression # # This is a temporary switch while experimenting with concrete syntax # One should be picked for inclusion in puppet. -lambda - : lambda_j8 - | lambda_ruby -# Java8-like lambda with parameters to the left of the body -lambda_j8 - : lambda_parameter_list optional_farrow lambda_rest { - result = Factory.LAMBDA(val[0], val[2]) +# Lambda with parameters to the left of the body +lambda + : lambda_parameter_list lambda_rest { + result = Factory.LAMBDA(val[0], val[1]) # loc result, val[1] # TODO } @@ -218,22 +215,6 @@ lambda_rest : LBRACE statements RBRACE { result = val[1] } | LBRACE RBRACE { result = nil } -optional_farrow - : nil - | FARROW - -# Ruby-like lambda with parameters inside the body -# -lambda_ruby - : LAMBDA lambda_parameter_list statements RBRACE { - result = Factory.LAMBDA(val[1], val[2]) - loc result, val[0], val[3] - } - | LAMBDA lambda_parameter_list RBRACE { - result = Factory.LAMBDA(val[1], nil) - loc result, val[0], val[2] - } - # Produces Array<Model::Parameter> lambda_parameter_list : PIPE PIPE { result = [] } diff --git a/lib/puppet/pops/parser/eparser.rb b/lib/puppet/pops/parser/eparser.rb index e476e0358..6d71e7b7e 100644 --- a/lib/puppet/pops/parser/eparser.rb +++ b/lib/puppet/pops/parser/eparser.rb @@ -20,7 +20,7 @@ module Puppet module Parser class Parser < Racc::Parser -module_eval(<<'...end egrammar.ra/module_eval...', 'egrammar.ra', 718) +module_eval(<<'...end egrammar.ra/module_eval...', 'egrammar.ra', 699) # Make emacs happy # Local Variables: @@ -30,125 +30,64 @@ module_eval(<<'...end egrammar.ra/module_eval...', 'egrammar.ra', 718) ##### State transition tables begin ### clist = [ -'68,-132,112,207,220,235,344,51,53,87,88,84,79,90,234,94,-130,89,51,53', -'80,82,81,83,234,283,224,269,222,115,233,223,321,114,207,234,244,204', -'93,-198,-207,-132,86,85,54,299,72,73,75,74,77,78,68,70,71,54,-130,316', -'202,315,69,87,88,84,79,90,59,94,76,89,51,53,80,82,81,83,245,59,115,-198', -'-207,301,114,115,115,51,53,114,114,115,93,330,291,114,86,85,105,104', -'72,73,75,74,77,78,68,70,71,120,225,227,122,226,69,87,88,84,79,90,68', -'94,76,89,54,297,80,82,81,83,68,59,115,90,268,94,114,89,243,51,53,105', -'104,90,93,94,128,89,86,85,68,267,72,73,75,74,77,78,93,70,71,84,79,90', -'68,94,69,89,93,306,80,82,81,83,76,192,54,90,316,94,315,89,309,70,71', -'105,104,310,93,207,69,51,53,85,68,168,72,73,75,74,77,78,93,70,71,84', -'79,90,313,94,69,89,51,53,80,82,81,83,76,105,68,68,317,51,53,229,228', -'319,120,63,263,122,93,90,90,94,94,89,89,241,72,73,75,74,77,78,243,70', -'71,120,59,326,122,327,69,68,241,91,93,93,120,267,76,122,87,88,84,79', -'90,259,94,59,89,70,71,80,82,81,83,68,69,63,59,64,66,65,67,134,258,257', -'336,79,90,93,94,243,89,86,85,80,243,72,73,75,74,77,78,116,70,71,241', -'339,217,341,106,69,217,93,282,286,319,346,347,76,348,72,73,75,74,77', -'78,68,70,71,349,99,352,353,354,69,285,63,60,79,90,361,94,76,89,362,363', -'80,364,,,68,,,,,,,,,,,,79,90,93,94,,89,,,80,,72,73,75,74,77,78,68,70', -'71,,,,,,69,,93,,,90,,94,76,89,72,73,75,74,77,78,68,70,71,,,,,,69,,,', -'79,90,93,94,76,89,,,80,,72,73,75,74,77,78,68,70,71,,,,,,69,,93,,,90', -',94,76,89,72,73,75,74,77,78,68,70,71,,,,,,69,87,88,84,79,90,93,94,76', -'89,,,80,82,81,83,68,,,,,70,71,,,,,,69,90,93,94,,89,86,85,,,72,73,75', -'74,77,78,68,70,71,,,,,,69,,93,,,90,,94,76,89,72,73,75,74,77,78,68,70', -'71,,,,,,69,,,,,90,93,94,76,89,,,,,72,73,75,74,,,,70,71,,,,,,69,,93,', -',,,,76,,72,73,75,74,,,68,70,71,,,,,,69,87,88,84,79,90,239,94,76,89,', -',80,82,81,83,,,,,,,,,,,,,,,93,,,,86,85,,,72,73,75,74,77,78,68,70,71', -',,,,,69,87,88,84,79,90,,94,76,89,,,80,82,81,83,,,,,,,,,,,,,,,93,,,,86', -'85,,,72,73,75,74,77,78,68,70,71,,,,,,69,87,88,84,79,90,,94,76,89,68', -',80,82,81,83,,,,,,,,90,,94,,89,,,93,,,,86,85,,,72,73,75,74,77,78,,70', -'71,,93,,,,69,,,,,,75,74,76,,68,70,71,,,,,,69,87,88,84,79,90,,94,76,89', -'68,,80,82,81,83,,,,,,,,90,,94,,89,,,93,,,,86,85,,,72,73,75,74,77,78', -',70,71,,93,,,,69,,,,,,75,74,76,,68,70,71,,,,,,69,87,88,84,79,90,,94', -'76,89,,,80,82,81,83,,,,,,,,,,,,,,,93,,,,86,85,,,72,73,75,74,77,78,68', -'70,71,,,,,,69,87,88,84,79,90,,94,76,89,,,80,82,81,83,,,,,,,,,,,,,,,93', -',,,86,85,,,72,73,75,74,77,78,68,70,71,,,,,,69,87,88,84,79,90,,94,76', -'89,,,80,82,81,83,,,,,,,,,,,,,,,93,,,,86,85,,,72,73,75,74,77,78,68,70', -'71,,,,,,69,87,88,84,79,90,,94,76,89,,,80,82,81,83,,,,,,,,,,,,,,,93,', -',,86,85,,,72,73,75,74,77,78,68,70,71,,,,,,69,87,88,84,79,90,,94,76,89', +'68,215,228,229,-126,230,202,229,238,87,88,84,79,90,290,94,262,89,-124', +'68,80,82,81,83,68,217,51,53,51,53,224,223,90,239,94,-192,89,90,93,94', +'199,89,86,85,-126,212,72,73,75,74,77,78,276,70,71,51,53,93,-124,68,69', +'202,93,117,-201,54,119,76,87,88,84,79,90,240,94,-192,89,70,71,80,82', +'81,83,68,69,59,229,59,51,53,292,212,117,109,312,119,90,93,94,112,89', +'86,85,111,-201,72,73,75,74,77,78,294,70,71,59,51,53,279,68,69,112,93', +'51,53,111,54,76,87,88,84,79,90,307,94,306,89,70,71,80,82,81,83,112,69', +'112,219,111,59,111,321,218,278,117,112,112,119,93,111,111,117,86,85', +'119,275,72,73,75,74,77,78,220,70,71,221,59,68,68,307,69,306,238,59,189', +'299,300,76,84,79,90,90,94,94,89,89,301,80,82,81,83,51,53,202,68,165', +'51,53,284,64,66,65,67,125,304,93,93,90,236,94,85,89,308,72,73,75,74', +'77,78,310,70,71,222,261,68,236,238,69,54,317,318,260,93,54,76,84,79', +'90,260,94,63,89,63,131,80,82,81,83,68,102,254,327,253,198,113,252,330', +'102,103,238,102,90,93,94,334,89,310,336,337,338,72,73,75,74,77,78,339', +'70,71,99,342,343,344,68,69,91,93,236,63,60,351,76,87,88,84,79,90,352', +'94,353,89,70,71,80,82,81,83,68,69,354,,,,,,,,,,79,90,93,94,,89,86,85', +'80,,72,73,75,74,77,78,,70,71,,,,,,69,,93,,,,,76,68,,72,73,75,74,77,78', +',70,71,,79,90,,94,69,89,,,80,,,76,68,,,,,,,,,,,,79,90,93,94,,89,,,80', +',72,73,75,74,77,78,,70,71,,,,,,69,,93,,,,,76,68,,72,73,75,74,77,78,', +'70,71,,79,90,,94,69,89,,,80,,,76,68,,,,,,,,,,,,,90,93,94,,89,,,,,72', +'73,75,74,77,78,,70,71,,,,,,69,,93,,,,,76,,,72,73,75,74,77,78,,70,71', +',,,,68,69,,,,,,,76,87,88,84,79,90,,94,,89,,,80,82,81,83,68,,,,,,,,,', +',,,90,93,94,,89,86,85,,,72,73,75,74,77,78,,70,71,,,,,,69,,93,,,,68,76', +',,72,73,75,74,77,78,,70,71,90,,94,,89,69,,,,,,68,76,,,,,,,,,,,,90,93', +'94,,89,,,,,72,73,75,74,,,,70,71,,,,,,69,,93,,,,,76,,,72,73,75,74,,,', +'70,71,,,,,68,69,,,,,,,76,87,88,84,79,90,234,94,,89,,,80,82,81,83,,,', +',,,,,,,,,,,93,,,,86,85,,,72,73,75,74,77,78,,70,71,,,,,68,69,,,,,,,76', +'87,88,84,79,90,,94,,89,,,80,82,81,83,,,,,,,,,,,,,,,93,,,,86,85,,,72', +'73,75,74,77,78,,70,71,,,,,68,69,,,,,,,76,87,88,84,79,90,,94,,89,,,80', +'82,81,83,68,,,,,,,,,,,,,90,93,94,,89,86,85,,,72,73,75,74,77,78,,70,71', +',,,,,69,,93,,,,,76,,,,,75,74,,,,70,71,,,,,68,69,,,,,,,76,87,88,84,79', +'90,,94,,89,,,80,82,81,83,68,,,,,,,,,,,,,90,93,94,,89,86,85,,,72,73,75', +'74,77,78,,70,71,,,,,,69,,93,,,,,76,,,,,75,74,,,,70,71,,,,,68,69,,,,', +',,76,87,88,84,79,90,,94,,89,,,80,82,81,83,,,,,,,,,,,,,,,93,,,,86,85', +',,72,73,75,74,77,78,,70,71,,,,,68,69,,,,,,,76,87,88,84,79,90,,94,,89', ',,80,82,81,83,,,,,,,,,,,,,,,93,,,,86,85,,,72,73,75,74,77,78,,70,71,', -',,,,69,68,,213,,,,,76,,87,88,84,79,90,,94,,89,,,80,82,81,83,,,,,,,,', -',,,,,,93,,,,86,85,,,72,73,75,74,77,78,,70,71,,,,,,69,68,,212,,,,,76', -',87,88,84,79,90,,94,,89,,,80,82,81,83,,,,,,,,,,,,,,,93,,,,86,85,,,72', -'73,75,74,77,78,,70,71,,,,,,69,68,,211,,,,,76,,87,88,84,79,90,,94,,89', -',,80,82,81,83,,,,,,,,,,,,,,,93,,,,86,85,,,72,73,75,74,77,78,,70,71,', -',,,,69,68,,210,,,,,76,,87,88,84,79,90,,94,,89,,,80,82,81,83,,,,,,,,', -',,,,,,93,,,,86,85,,,72,73,75,74,77,78,68,70,71,,,,,,69,87,88,84,79,90', -',94,76,89,,197,80,82,81,83,,,,,,,,,,,,,,,93,,,,86,85,,,72,73,75,74,77', -'78,,70,71,51,53,,,47,69,48,,,,,,,76,,,,,,,,,13,,,,,,38,,44,,46,96,,45', -'58,54,,40,57,,,,55,12,,,56,51,53,11,,47,,48,,,,59,,,,,,39,,,167,,,13', -',,,,,170,187,181,188,46,182,190,183,179,177,,172,185,,,,55,12,191,186', -'184,51,53,11,,47,,48,333,,,59,,,,,189,171,,,,,,13,,,,,,38,,44,,46,42', -',45,58,54,,40,57,43,,,55,12,51,53,56,,47,11,48,322,,,,,,59,,,,,,39,', -',13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48', -',,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51', -'53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,', -'40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44', -',46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39', -',,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48', -',,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51', -'53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,', -'40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44', -',46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39', -',,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48', -',,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51', -'53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,', -'40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44', -',46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39', -',,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48', -',,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51', -'53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,', -'40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44', -',46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39', -',,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48', -',,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51', -'53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,', -'40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44', -',46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39', -',,13,,,,,,170,187,181,188,46,182,190,183,179,177,,172,185,,,,55,12,191', -'186,184,51,53,11,,47,,48,308,,,59,,,,,189,171,,,,,,13,,,,,,38,,44,,46', -'42,,45,58,54,,40,57,43,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,', -',13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48', -',,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51', -'53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,', -'40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44', -',46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39', -',,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48', -',,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,42,,45,58,54,,40,57,43,,,55,12', -'51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,199,,,,,38,,44,,46,96,,45,58', -'54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38', -',44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,', -',,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47', -'11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55', -'12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,209,,,,,38,,44,,46,96,,45', -'58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,', -'38,,44,,46,42,,45,58,54,,40,57,43,,,55,12,51,53,56,,47,11,48,,,,,,,59', -',,,,,39,,,13,,,,,,38,,44,,46,42,,45,58,54,,40,57,43,,,55,12,51,53,56', -',47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,42,,45,58,54,,40,57', -'43,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46', -'42,,45,58,54,,40,57,43,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,', -',13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48', -',,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51', -'53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,', -'40,57,,,,55,12,,,56,51,53,11,,47,290,48,,,,59,,,,,,39,,,,,,13,,,,,,38', -',44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,335,,,,,,59', -',,,,,39,,,13,,,,,,38,,44,,46,42,,45,58,54,,40,57,43,,,55,12,51,53,56', -',47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57', -',,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96', -',45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,273,,,,,,59,,,,,,39,,,13', -',,,,,38,,44,,46,42,,45,58,54,,40,57,43,,,55,12,51,53,56,,47,11,48,,', -',,,,59,,,,,,39,,,13,,,,,,38,,44,,46,42,,45,58,54,,40,57,43,,,55,12,51', -'53,56,,47,11,48,271,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,42,,45,58,54', -',40,57,43,,,55,12,51,53,56,,47,11,48,265,,,,,,59,,,,,,39,,,13,,,,,,38', -',44,,46,42,,45,58,54,,40,57,43,,,55,12,51,53,56,,47,11,48,,,,,,,59,', -',,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47', -'11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55', -'12,,,56,51,53,11,,47,126,48,,,,59,,,,,,39,,,,,,13,,,,,,38,,44,,46,96', -',45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,', -',,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,,', -',59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53', +',,,68,69,,,,,,,76,87,88,84,79,90,,94,,89,,,80,82,81,83,,,,,,,,,,,,,', +',93,,,,86,85,,,72,73,75,74,77,78,,70,71,,,,,68,69,,,,,,,76,87,88,84', +'79,90,,94,,89,,,80,82,81,83,,,,,,,,,,,,,,,93,,,,86,85,,,72,73,75,74', +'77,78,,70,71,,,,,68,69,,,,,,,76,87,88,84,79,90,,94,,89,,,80,82,81,83', +',,,,,,,,,,,,,,93,,,,86,85,,,72,73,75,74,77,78,,70,71,,,,,68,69,208,', +',,,,76,87,88,84,79,90,,94,,89,,,80,82,81,83,,,,,,,,,,,,,,,93,,,,86,85', +',,72,73,75,74,77,78,,70,71,,,,,68,69,207,,,,,,76,87,88,84,79,90,,94', +',89,,,80,82,81,83,,,,,,,,,,,,,,,93,,,,86,85,,,72,73,75,74,77,78,,70', +'71,,,,,68,69,206,,,,,,76,87,88,84,79,90,,94,,89,,,80,82,81,83,,,,,,', +',,,,,,,,93,,,,86,85,,,72,73,75,74,77,78,,70,71,,,,,68,69,205,,,,,,76', +'87,88,84,79,90,,94,,89,,,80,82,81,83,,,,,,,,,,,,,,,93,,,,86,85,,,72', +'73,75,74,77,78,,70,71,,,,,68,69,,,,,,,76,87,88,84,79,90,,94,,89,,194', +'80,82,81,83,,,,,,,,,,51,53,,,47,93,48,,,86,85,,,72,73,75,74,77,78,,70', +'71,13,,,,,69,38,,44,,46,96,76,45,58,54,,40,57,,,,55,12,51,53,56,,47', +'11,48,,,,,,,59,,,,,,39,,164,13,,,,,,167,184,178,185,46,179,187,180,176', +'174,,169,182,,,,55,12,188,183,181,51,53,11,,47,,48,324,,,59,,,,,186', +'168,,,,,,13,,,,,,38,,44,,46,42,,45,58,54,,40,57,43,,,55,12,51,53,56', +',47,11,48,313,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40', +'57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46', +'96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13', +',,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,', +',,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53', '56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40', '57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46', '96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13', @@ -156,24 +95,85 @@ clist = [ ',,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53', '56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40', '57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46', -'96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,351,,,,,,59,,,,,,39', -',,13,,,,,,38,,44,,46,42,,45,58,54,,40,57,43,,,55,12,51,53,56,,47,11', +'96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13', +',,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,', +',,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53', +'56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40', +'57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46', +'96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13', +',,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,', +',,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53', +'56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40', +'57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46', +'96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13', +',,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,', +',,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53', +'56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,167,184,178,185,46,179,187,180', +'176,174,,169,182,,,,55,12,188,183,181,51,53,11,,47,,48,,,,59,,,,,186', +'168,,,,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47', +'11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55', +'12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58', +'54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38', +',44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,', +',,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47', +'11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55', +'12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,42,,45,58', +'54,,40,57,43,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,196,,', +',,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59', +',,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,', +'47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,', +',,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,204,,,,,38,,44,,46', +'96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13', +',,,,,38,,44,,46,42,,45,58,54,,40,57,43,,,55,12,51,53,56,,47,11,48,,', +',,,,59,,,,,,39,,,13,,,,,,38,,44,,46,42,,45,58,54,,40,57,43,,,55,12,51', +'53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,42,,45,58,54,', +'40,57,43,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44', +',46,42,,45,58,54,,40,57,43,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,', +'39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11', '48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12', '51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54', -',40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44', -',46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,356,,,,,,59,,,,', -',39,,,13,,,,,,38,,44,,46,42,,45,58,54,,40,57,43,,,55,12,51,53,56,,47', -'11,48,358,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,42,,45,58,54,,40,57,43', -',,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,42', -',45,58,54,61,40,57,43,,,55,12,51,53,56,,47,11,48,360,,,,,,59,,,,,,39', +',40,57,,,,55,12,,,56,51,53,11,,47,283,48,,,,59,,,,,,39,,,,,,13,,,,,', +'38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,326,,,,,', +'59,,,,,,39,,,13,,,,,,38,,44,,46,42,,45,58,54,,40,57,43,,,55,12,51,53', +'56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40', +'57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46', +'96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,266,,,,,,59,,,,,,39', ',,13,,,,,,38,,44,,46,42,,45,58,54,,40,57,43,,,55,12,51,53,56,,47,11', '48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,42,,45,58,54,,40,57,43,,,55', '12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58', -'54,,40,57,,,,55,12,,,56,,,11,,,,253,187,252,188,59,250,190,254,248,247', -'39,249,251,,,,,,191,186,255,253,187,252,188,,250,190,254,248,247,,249', -'251,,,189,256,,191,186,255,253,187,252,188,,250,190,254,248,247,,249', -'251,,,189,256,,191,186,255,,,,,,,,,,,,,,,,189,256' ] - racc_action_table = arr = ::Array.new(4804, nil) +'54,,40,57,,,,55,12,51,53,56,,47,11,48,258,,,,,,59,,,,,,39,,,13,,,,,', +'38,,44,,46,42,,45,58,54,,40,57,43,,,55,12,51,53,56,,47,11,48,,,,,,,59', +',,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,', +'47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,', +',,55,12,,,56,51,53,11,,47,123,48,,,,59,,,,,,39,,,,,,13,,,,,,38,,44,', +'46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39', +',,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48', +',,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51', +'53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,', +'40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44', +',46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39', +',,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48', +',,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55,12,51', +'53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,', +'40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44', +',46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,341,,,,,,59,,,,', +',39,,,13,,,,,,38,,44,,46,42,,45,58,54,,40,57,43,,,55,12,51,53,56,,47', +'11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58,54,,40,57,,,,55', +'12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,96,,45,58', +'54,,40,57,,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38', +',44,,46,96,,45,58,54,,40,57,,,,55,12,51,53,56,,47,11,48,346,,,,,,59', +',,,,,39,,,13,,,,,,38,,44,,46,42,,45,58,54,,40,57,43,,,55,12,51,53,56', +',47,11,48,348,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,42,,45,58,54,,40', +'57,43,,,55,12,51,53,56,,47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,', +'46,42,,45,58,54,61,40,57,43,,,55,12,51,53,56,,47,11,48,350,,,,,,59,', +',,,,39,,,13,,,,,,38,,44,,46,42,,45,58,54,,40,57,43,,,55,12,51,53,56', +',47,11,48,,,,,,,59,,,,,,39,,,13,,,,,,38,,44,,46,42,,45,58,54,,40,57', +'43,,,55,12,51,53,56,,47,11,48,264,,,,,,59,,,,,,39,,,13,,,,,,38,,44,', +'46,42,,45,58,54,,40,57,43,,,55,12,,,56,,,11,,,,248,184,247,185,59,245', +'187,249,243,242,39,244,246,,,,,,188,183,250,248,184,247,185,,245,187', +'249,243,242,,244,246,,,186,251,,188,183,250,248,184,247,185,,245,187', +'249,243,242,,244,246,,,186,251,,188,183,250,,,,,,,,,,,,,,,,186,251' ] + racc_action_table = arr = ::Array.new(4874, nil) idx = 0 clist.each do |str| str.split(',', -1).each do |i| @@ -183,167 +183,167 @@ clist = [ end clist = [ -'164,179,42,105,118,164,316,70,70,164,164,164,164,164,208,164,177,164', -'71,71,164,164,164,164,274,217,125,208,118,42,141,125,274,42,217,141', -'180,105,164,185,184,179,164,164,70,240,164,164,164,164,164,164,163,164', -'164,71,177,271,103,271,164,163,163,163,163,163,70,163,164,163,220,220', -'163,163,163,163,180,71,96,185,184,242,96,182,282,226,226,182,282,181', -'163,282,226,181,163,163,306,306,163,163,163,163,163,163,162,163,163', -'220,127,130,220,127,163,162,162,162,162,162,97,162,163,162,226,236,162', -'162,162,162,151,220,44,97,207,97,44,97,246,48,48,199,199,151,162,151', -'48,151,162,162,161,205,162,162,162,162,162,162,97,162,162,161,161,161', -'142,161,162,161,151,260,161,161,161,161,162,92,48,142,313,142,313,142', -'264,151,151,36,36,266,161,267,151,183,183,161,160,90,161,161,161,161', -'161,161,142,161,161,160,160,160,270,160,161,160,45,45,160,160,160,160', -'161,104,150,95,272,222,222,133,133,273,183,135,200,183,160,150,95,150', -'95,150,95,277,160,160,160,160,160,160,278,160,160,45,183,279,45,280', -'160,10,214,10,150,95,222,284,160,222,10,10,10,10,10,198,10,45,10,150', -'150,10,10,10,10,159,150,62,222,7,7,7,7,60,196,194,296,159,159,10,159', -'174,159,10,10,159,298,10,10,10,10,10,10,43,10,10,173,305,113,307,37', -'10,117,159,215,219,317,319,320,10,324,159,159,159,159,159,159,158,159', -'159,325,35,331,332,334,159,218,5,1,158,158,350,158,159,158,355,357,158', -'359,,,157,,,,,,,,,,,,157,157,158,157,,157,,,157,,158,158,158,158,158', -'158,155,158,158,,,,,,158,,157,,,155,,155,158,155,157,157,157,157,157', -'157,156,157,157,,,,,,157,,,,156,156,155,156,157,156,,,156,,155,155,155', -'155,155,155,149,155,155,,,,,,155,,156,,,149,,149,155,149,156,156,156', -'156,156,156,312,156,156,,,,,,156,312,312,312,312,312,149,312,156,312', -',,312,312,312,312,154,,,,,149,149,,,,,,149,154,312,154,,154,312,312', -',,312,312,312,312,312,312,153,312,312,,,,,,312,,154,,,153,,153,312,153', -'154,154,154,154,154,154,152,154,154,,,,,,154,,,,,152,153,152,154,152', -',,,,153,153,153,153,,,,153,153,,,,,,153,,152,,,,,,153,,152,152,152,152', -',,169,152,152,,,,,,152,169,169,169,169,169,169,169,152,169,,,169,169', -'169,169,,,,,,,,,,,,,,,169,,,,169,169,,,169,169,169,169,169,169,304,169', -'169,,,,,,169,304,304,304,304,304,,304,169,304,,,304,304,304,304,,,,', -',,,,,,,,,,304,,,,304,304,,,304,304,304,304,304,304,303,304,304,,,,,', -'304,303,303,303,303,303,,303,304,303,148,,303,303,303,303,,,,,,,,148', -',148,,148,,,303,,,,303,303,,,303,303,303,303,303,303,,303,303,,148,', -',,303,,,,,,148,148,303,,295,148,148,,,,,,148,295,295,295,295,295,,295', -'148,295,147,,295,295,295,295,,,,,,,,147,,147,,147,,,295,,,,295,295,', -',295,295,295,295,295,295,,295,295,,147,,,,295,,,,,,147,147,295,,293', -'147,147,,,,,,147,293,293,293,293,293,,293,147,293,,,293,293,293,293', -',,,,,,,,,,,,,,293,,,,293,293,,,293,293,293,293,293,293,193,293,293,', -',,,,293,193,193,193,193,193,,193,293,193,,,193,193,193,193,,,,,,,,,', -',,,,,193,,,,193,193,,,193,193,193,193,193,193,289,193,193,,,,,,193,289', -'289,289,289,289,,289,193,289,,,289,289,289,289,,,,,,,,,,,,,,,289,,,', -'289,289,,,289,289,289,289,289,289,131,289,289,,,,,,289,131,131,131,131', -'131,,131,289,131,,,131,131,131,131,,,,,,,,,,,,,,,131,,,,131,131,,,131', -'131,131,131,131,131,124,131,131,,,,,,131,124,124,124,124,124,,124,131', -'124,,,124,124,124,124,,,,,,,,,,,,,,,124,,,,124,124,,,124,124,124,124', -'124,124,,124,124,,,,,,124,111,,111,,,,,124,,111,111,111,111,111,,111', -',111,,,111,111,111,111,,,,,,,,,,,,,,,111,,,,111,111,,,111,111,111,111', -'111,111,,111,111,,,,,,111,110,,110,,,,,111,,110,110,110,110,110,,110', -',110,,,110,110,110,110,,,,,,,,,,,,,,,110,,,,110,110,,,110,110,110,110', -'110,110,,110,110,,,,,,110,109,,109,,,,,110,,109,109,109,109,109,,109', -',109,,,109,109,109,109,,,,,,,,,,,,,,,109,,,,109,109,,,109,109,109,109', -'109,109,,109,109,,,,,,109,107,,107,,,,,109,,107,107,107,107,107,,107', -',107,,,107,107,107,107,,,,,,,,,,,,,,,107,,,,107,107,,,107,107,107,107', -'107,107,98,107,107,,,,,,107,98,98,98,98,98,,98,107,98,,98,98,98,98,98', -',,,,,,,,,,,,,,98,,,,98,98,,,98,98,98,98,98,98,,98,98,89,89,,,89,98,89', -',,,,,,98,,,,,,,,,89,,,,,,89,,89,,89,89,,89,89,89,,89,89,,,,89,89,,,89', -'213,213,89,,213,,213,,,,89,,,,,,89,,,89,,,213,,,,,,213,213,213,213,213', -'213,213,213,213,213,,213,213,,,,213,213,213,213,213,285,285,213,,285', -',285,285,,,213,,,,,213,213,,,,,,285,,,,,,285,,285,,285,285,,285,285', -'285,,285,285,285,,,285,285,275,275,285,,275,285,275,275,,,,,,285,,,', -',,285,,,275,,,,,,275,,275,,275,275,,275,275,275,,275,275,,,,275,275', -'72,72,275,,72,275,72,,,,,,,275,,,,,,275,,,72,,,,,,72,,72,,72,72,,72', -'72,72,,72,72,,,,72,72,73,73,72,,73,72,73,,,,,,,72,,,,,,72,,,73,,,,,', -'73,,73,,73,73,,73,73,73,,73,73,,,,73,73,74,74,73,,74,73,74,,,,,,,73', -',,,,,73,,,74,,,,,,74,,74,,74,74,,74,74,74,,74,74,,,,74,74,75,75,74,', -'75,74,75,,,,,,,74,,,,,,74,,,75,,,,,,75,,75,,75,75,,75,75,75,,75,75,', -',,75,75,76,76,75,,76,75,76,,,,,,,75,,,,,,75,,,76,,,,,,76,,76,,76,76', -',76,76,76,,76,76,,,,76,76,77,77,76,,77,76,77,,,,,,,76,,,,,,76,,,77,', -',,,,77,,77,,77,77,,77,77,77,,77,77,,,,77,77,78,78,77,,78,77,78,,,,,', -',77,,,,,,77,,,78,,,,,,78,,78,,78,78,,78,78,78,,78,78,,,,78,78,79,79', -'78,,79,78,79,,,,,,,78,,,,,,78,,,79,,,,,,79,,79,,79,79,,79,79,79,,79', -'79,,,,79,79,80,80,79,,80,79,80,,,,,,,79,,,,,,79,,,80,,,,,,80,,80,,80', -'80,,80,80,80,,80,80,,,,80,80,81,81,80,,81,80,81,,,,,,,80,,,,,,80,,,81', -',,,,,81,,81,,81,81,,81,81,81,,81,81,,,,81,81,82,82,81,,82,81,82,,,,', -',,81,,,,,,81,,,82,,,,,,82,,82,,82,82,,82,82,82,,82,82,,,,82,82,83,83', -'82,,83,82,83,,,,,,,82,,,,,,82,,,83,,,,,,83,,83,,83,83,,83,83,83,,83', -'83,,,,83,83,84,84,83,,84,83,84,,,,,,,83,,,,,,83,,,84,,,,,,84,,84,,84', -'84,,84,84,84,,84,84,,,,84,84,85,85,84,,85,84,85,,,,,,,84,,,,,,84,,,85', -',,,,,85,,85,,85,85,,85,85,85,,85,85,,,,85,85,86,86,85,,86,85,86,,,,', -',,85,,,,,,85,,,86,,,,,,86,,86,,86,86,,86,86,86,,86,86,,,,86,86,167,167', -'86,,167,86,167,,,,,,,86,,,,,,86,,,167,,,,,,167,,167,,167,167,,167,167', -'167,,167,167,,,,167,167,88,88,167,,88,167,88,,,,,,,167,,,,,,167,,,88', -',,,,,88,,88,,88,88,,88,88,88,,88,88,,,,88,88,68,68,88,,68,88,68,,,,', -',,88,,,,,,88,,,68,,,,,,68,,68,,68,68,,68,68,68,,68,68,,,,68,68,268,268', -'68,,268,68,268,,,,,,,68,,,,,,68,,,268,,,,,,268,,268,,268,268,,268,268', -'268,,268,268,,,,268,268,91,91,268,,91,268,91,,,,,,,268,,,,,,268,,,91', -',,,,,91,91,91,91,91,91,91,91,91,91,,91,91,,,,91,91,91,91,91,263,263', -'91,,263,,263,263,,,91,,,,,91,91,,,,,,263,,,,,,263,,263,,263,263,,263', -'263,263,,263,263,263,,,263,263,93,93,263,,93,263,93,,,,,,,263,,,,,,263', -',,93,,,,,,93,,93,,93,93,,93,93,93,,93,93,,,,93,93,94,94,93,,94,93,94', -',,,,,,93,,,,,,93,,,94,,,,,,94,,94,,94,94,,94,94,94,,94,94,,,,94,94,259', -'259,94,,259,94,259,,,,,,,94,,,,,,94,,,259,,,,,,259,,259,,259,259,,259', -'259,259,,259,259,,,,259,259,245,245,259,,245,259,245,,,,,,,259,,,,,', -'259,,,245,,,,,,245,,245,,245,245,,245,245,245,,245,245,,,,245,245,244', -'244,245,,244,245,244,,,,,,,245,,,,,,245,,,244,,,,,,244,,244,,244,244', -',244,244,244,,244,244,,,,244,244,67,67,244,,67,244,67,,,,,,,244,,,,', -',244,,,67,,,,,,67,,67,,67,67,,67,67,67,,67,67,67,,,67,67,99,99,67,,99', -'67,99,,,,,,,67,,,,,,67,,,99,99,,,,,99,,99,,99,99,,99,99,99,,99,99,,', -',99,99,241,241,99,,241,99,241,,,,,,,99,,,,,,99,,,241,,,,,,241,,241,', -'241,241,,241,241,241,,241,241,,,,241,241,235,235,241,,235,241,235,,', -',,,,241,,,,,,241,,,235,,,,,,235,,235,,235,235,,235,235,235,,235,235', -',,,235,235,234,234,235,,234,235,234,,,,,,,235,,,,,,235,,,234,,,,,,234', -',234,,234,234,,234,234,234,,234,234,,,,234,234,106,106,234,,106,234', -'106,,,,,,,234,,,,,,234,,,106,106,,,,,106,,106,,106,106,,106,106,106', -',106,106,,,,106,106,66,66,106,,66,106,66,,,,,,,106,,,,,,106,,,66,,,', -',,66,,66,,66,66,,66,66,66,,66,66,66,,,66,66,65,65,66,,65,66,65,,,,,', -',66,,,,,,66,,,65,,,,,,65,,65,,65,65,,65,65,65,,65,65,65,,,65,65,64,64', -'65,,64,65,64,,,,,,,65,,,,,,65,,,64,,,,,,64,,64,,64,64,,64,64,64,,64', -'64,64,,,64,64,63,63,64,,63,64,63,,,,,,,64,,,,,,64,,,63,,,,,,63,,63,', -'63,63,,63,63,63,,63,63,63,,,63,63,112,112,63,,112,63,112,,,,,,,63,,', -',,,63,,,112,,,,,,112,,112,,112,112,,112,112,112,,112,112,,,,112,112', -'232,232,112,,232,112,232,,,,,,,112,,,,,,112,,,232,,,,,,232,,232,,232', -'232,,232,232,232,,232,232,,,,232,232,227,227,232,,227,232,227,,,,,,', -'232,,,,,,232,,,227,,,,,,227,,227,,227,227,,227,227,227,,227,227,,,,227', -'227,,,227,223,223,227,,223,223,223,,,,227,,,,,,227,,,,,,223,,,,,,223', -',223,,223,223,,223,223,223,,223,223,,,,223,223,286,286,223,,286,223', -'286,286,,,,,,223,,,,,,223,,,286,,,,,,286,,286,,286,286,,286,286,286', -',286,286,286,,,286,286,69,69,286,,69,286,69,,,,,,,286,,,,,,286,,,69', -',,,,,69,,69,,69,69,,69,69,69,,69,69,,,,69,69,212,212,69,,212,69,212', -',,,,,,69,,,,,,69,,,212,,,,,,212,,212,,212,212,,212,212,212,,212,212', -',,,212,212,211,211,212,,211,212,211,211,,,,,,212,,,,,,212,,,211,,,,', -',211,,211,,211,211,,211,211,211,,211,211,211,,,211,211,61,61,211,,61', -'211,61,,,,,,,211,,,,,,211,,,61,,,,,,61,,61,,61,61,,61,61,61,,61,61,61', -',,61,61,210,210,61,,210,61,210,210,,,,,,61,,,,,,61,,,210,,,,,,210,,210', -',210,210,,210,210,210,,210,210,210,,,210,210,203,203,210,,203,210,203', -'203,,,,,,210,,,,,,210,,,203,,,,,,203,,203,,203,203,,203,203,203,,203', -'203,203,,,203,203,52,52,203,,52,203,52,,,,,,,203,,,,,,203,,,52,,,,,', -'52,,52,,52,52,,52,52,52,,52,52,,,,52,52,172,172,52,,172,52,172,,,,,', -',52,,,,,,52,,,172,,,,,,172,,172,,172,172,,172,172,172,,172,172,,,,172', -'172,,,172,47,47,172,,47,47,47,,,,172,,,,,,172,,,,,,47,,,,,,47,,47,,47', -'47,,47,47,47,,47,47,,,,47,47,297,297,47,,297,47,297,,,,,,,47,,,,,,47', -',,297,,,,,,297,,297,,297,297,,297,297,297,,297,297,,,,297,297,171,171', -'297,,171,297,171,,,,,,,297,,,,,,297,,,171,,,,,,171,,171,,171,171,,171', -'171,171,,171,171,,,,171,171,170,170,171,,170,171,170,,,,,,,171,,,,,', -'171,,,170,,,,,,170,,170,,170,170,,170,170,170,,170,170,,,,170,170,41', -'41,170,,41,170,41,,,,,,,170,,,,,,170,,,41,,,,,,41,,41,,41,41,,41,41', -'41,,41,41,,,,41,41,40,40,41,,40,41,40,,,,,,,41,,,,,,41,,,40,,,,,,40', -',40,,40,40,,40,40,40,,40,40,,,,40,40,39,39,40,,39,40,39,,,,,,,40,,,', -',,40,,,39,,,,,,39,,39,,39,39,,39,39,39,,39,39,,,,39,39,38,38,39,,38', -'39,38,,,,,,,39,,,,,,39,,,38,,,,,,38,,38,,38,38,,38,38,38,,38,38,,,,38', -'38,315,315,38,,315,38,315,,,,,,,38,,,,,,38,,,315,,,,,,315,,315,,315', -'315,,315,315,315,,315,315,,,,315,315,327,327,315,,327,315,327,327,,', -',,,315,,,,,,315,,,327,,,,,,327,,327,,327,327,,327,327,327,,327,327,327', -',,327,327,13,13,327,,13,327,13,,,,,,,327,,,,,,327,,,13,,,,,,13,,13,', -'13,13,,13,13,13,,13,13,,,,13,13,12,12,13,,12,13,12,,,,,,,13,,,,,,13', -',,12,,,,,,12,,12,,12,12,,12,12,12,,12,12,,,,12,12,11,11,12,,11,12,11', -',,,,,,12,,,,,,12,,,11,,,,,,11,,11,,11,11,,11,11,11,,11,11,,,,11,11,344', -'344,11,,344,11,344,344,,,,,,11,,,,,,11,,,344,,,,,,344,,344,,344,344', -',344,344,344,,344,344,344,,,344,344,346,346,344,,346,344,346,346,,,', -',,344,,,,,,344,,,346,,,,,,346,,346,,346,346,,346,346,346,,346,346,346', -',,346,346,4,4,346,,4,346,4,,,,,,,346,,,,,,346,,,4,,,,,,4,,4,,4,4,,4', -'4,4,4,4,4,4,,,4,4,347,347,4,,347,4,347,347,,,,,,4,,,,,,4,,,347,,,,,', -'347,,347,,347,347,,347,347,347,,347,347,347,,,347,347,0,0,347,,0,347', -'0,,,,,,,347,,,,,,347,,,0,,,,,,0,,0,,0,0,,0,0,0,,0,0,0,,,0,0,87,87,0', -',87,0,87,,,,,,,0,,,,,,0,,,87,,,,,,87,,87,,87,87,,87,87,87,,87,87,,,', -'87,87,,,87,,,87,,,,192,192,192,192,87,192,192,192,192,192,87,192,192', -',,,,,192,192,192,238,238,238,238,,238,238,238,238,238,,238,238,,,192', -'192,,238,238,238,243,243,243,243,,243,243,243,243,243,,243,243,,,238', -'238,,243,243,243,,,,,,,,,,,,,,,,243,243' ] - racc_action_check = arr = ::Array.new(4804, nil) +'161,115,138,203,176,161,102,138,291,161,161,161,161,161,231,161,203', +'161,174,139,161,161,161,161,148,115,215,215,71,71,130,130,139,177,139', +'182,139,148,161,148,102,148,161,161,176,110,161,161,161,161,161,161', +'212,161,161,217,217,139,174,160,161,212,148,215,181,71,215,161,160,160', +'160,160,160,177,160,182,160,148,148,160,160,160,160,147,148,215,267', +'71,70,70,235,114,217,42,267,217,147,160,147,96,147,160,160,96,181,160', +'160,160,160,160,160,237,160,160,217,180,180,214,159,160,42,147,45,45', +'42,70,160,159,159,159,159,159,264,159,264,159,147,147,159,159,159,159', +'275,147,179,122,275,70,179,275,122,213,180,44,178,180,159,44,178,45', +'159,159,45,210,159,159,159,159,159,159,124,159,159,124,180,158,97,304', +'159,304,241,45,92,255,257,159,158,158,158,97,158,97,158,97,259,158,158', +'158,158,221,221,260,95,90,48,48,221,7,7,7,7,48,263,158,97,95,209,95', +'158,95,265,158,158,158,158,158,158,266,158,158,127,202,157,270,271,158', +'221,272,273,200,95,48,158,157,157,157,277,157,132,157,62,60,157,157', +'157,157,146,196,195,289,193,101,43,191,298,299,37,171,36,146,157,146', +'307,146,308,310,311,315,157,157,157,157,157,157,316,157,157,35,322,323', +'325,10,157,10,146,170,5,1,340,157,10,10,10,10,10,345,10,347,10,146,146', +'10,10,10,10,156,146,349,,,,,,,,,,156,156,10,156,,156,10,10,156,,10,10', +'10,10,10,10,,10,10,,,,,,10,,156,,,,,10,155,,156,156,156,156,156,156', +',156,156,,155,155,,155,156,155,,,155,,,156,154,,,,,,,,,,,,154,154,155', +'154,,154,,,154,,155,155,155,155,155,155,,155,155,,,,,,155,,154,,,,,155', +'153,,154,154,154,154,154,154,,154,154,,153,153,,153,154,153,,,153,,', +'154,152,,,,,,,,,,,,,152,153,152,,152,,,,,153,153,153,153,153,153,,153', +'153,,,,,,153,,152,,,,,153,,,152,152,152,152,152,152,,152,152,,,,,303', +'152,,,,,,,152,303,303,303,303,303,,303,,303,,,303,303,303,303,151,,', +',,,,,,,,,,151,303,151,,151,303,303,,,303,303,303,303,303,303,,303,303', +',,,,,303,,151,,,,150,303,,,151,151,151,151,151,151,,151,151,150,,150', +',150,151,,,,,,149,151,,,,,,,,,,,,149,150,149,,149,,,,,150,150,150,150', +',,,150,150,,,,,,150,,149,,,,,150,,,149,149,149,149,,,,149,149,,,,,166', +'149,,,,,,,149,166,166,166,166,166,166,166,,166,,,166,166,166,166,,,', +',,,,,,,,,,,166,,,,166,166,,,166,166,166,166,166,166,,166,166,,,,,297', +'166,,,,,,,166,297,297,297,297,297,,297,,297,,,297,297,297,297,,,,,,', +',,,,,,,,297,,,,297,297,,,297,297,297,297,297,297,,297,297,,,,,296,297', +',,,,,,297,296,296,296,296,296,,296,,296,,,296,296,296,296,145,,,,,,', +',,,,,,145,296,145,,145,296,296,,,296,296,296,296,296,296,,296,296,,', +',,,296,,145,,,,,296,,,,,145,145,,,,145,145,,,,,288,145,,,,,,,145,288', +'288,288,288,288,,288,,288,,,288,288,288,288,144,,,,,,,,,,,,,144,288', +'144,,144,288,288,,,288,288,288,288,288,288,,288,288,,,,,,288,,144,,', +',,288,,,,,144,144,,,,144,144,,,,,286,144,,,,,,,144,286,286,286,286,286', +',286,,286,,,286,286,286,286,,,,,,,,,,,,,,,286,,,,286,286,,,286,286,286', +'286,286,286,,286,286,,,,,190,286,,,,,,,286,190,190,190,190,190,,190', +',190,,,190,190,190,190,,,,,,,,,,,,,,,190,,,,190,190,,,190,190,190,190', +'190,190,,190,190,,,,,282,190,,,,,,,190,282,282,282,282,282,,282,,282', +',,282,282,282,282,,,,,,,,,,,,,,,282,,,,282,282,,,282,282,282,282,282', +'282,,282,282,,,,,128,282,,,,,,,282,128,128,128,128,128,,128,,128,,,128', +'128,128,128,,,,,,,,,,,,,,,128,,,,128,128,,,128,128,128,128,128,128,', +'128,128,,,,,121,128,,,,,,,128,121,121,121,121,121,,121,,121,,,121,121', +'121,121,,,,,,,,,,,,,,,121,,,,121,121,,,121,121,121,121,121,121,,121', +'121,,,,,108,121,108,,,,,,121,108,108,108,108,108,,108,,108,,,108,108', +'108,108,,,,,,,,,,,,,,,108,,,,108,108,,,108,108,108,108,108,108,,108', +'108,,,,,107,108,107,,,,,,108,107,107,107,107,107,,107,,107,,,107,107', +'107,107,,,,,,,,,,,,,,,107,,,,107,107,,,107,107,107,107,107,107,,107', +'107,,,,,106,107,106,,,,,,107,106,106,106,106,106,,106,,106,,,106,106', +'106,106,,,,,,,,,,,,,,,106,,,,106,106,,,106,106,106,106,106,106,,106', +'106,,,,,104,106,104,,,,,,106,104,104,104,104,104,,104,,104,,,104,104', +'104,104,,,,,,,,,,,,,,,104,,,,104,104,,,104,104,104,104,104,104,,104', +'104,,,,,98,104,,,,,,,104,98,98,98,98,98,,98,,98,,98,98,98,98,98,,,,', +',,,,,89,89,,,89,98,89,,,98,98,,,98,98,98,98,98,98,,98,98,89,,,,,98,89', +',89,,89,89,98,89,89,89,,89,89,,,,89,89,208,208,89,,208,89,208,,,,,,', +'89,,,,,,89,,89,208,,,,,,208,208,208,208,208,208,208,208,208,208,,208', +'208,,,,208,208,208,208,208,278,278,208,,278,,278,278,,,208,,,,,208,208', +',,,,,278,,,,,,278,,278,,278,278,,278,278,278,,278,278,278,,,278,278', +'268,268,278,,268,278,268,268,,,,,,278,,,,,,278,,,268,,,,,,268,,268,', +'268,268,,268,268,268,,268,268,,,,268,268,72,72,268,,72,268,72,,,,,,', +'268,,,,,,268,,,72,,,,,,72,,72,,72,72,,72,72,72,,72,72,,,,72,72,73,73', +'72,,73,72,73,,,,,,,72,,,,,,72,,,73,,,,,,73,,73,,73,73,,73,73,73,,73', +'73,,,,73,73,74,74,73,,74,73,74,,,,,,,73,,,,,,73,,,74,,,,,,74,,74,,74', +'74,,74,74,74,,74,74,,,,74,74,75,75,74,,75,74,75,,,,,,,74,,,,,,74,,,75', +',,,,,75,,75,,75,75,,75,75,75,,75,75,,,,75,75,76,76,75,,76,75,76,,,,', +',,75,,,,,,75,,,76,,,,,,76,,76,,76,76,,76,76,76,,76,76,,,,76,76,77,77', +'76,,77,76,77,,,,,,,76,,,,,,76,,,77,,,,,,77,,77,,77,77,,77,77,77,,77', +'77,,,,77,77,78,78,77,,78,77,78,,,,,,,77,,,,,,77,,,78,,,,,,78,,78,,78', +'78,,78,78,78,,78,78,,,,78,78,79,79,78,,79,78,79,,,,,,,78,,,,,,78,,,79', +',,,,,79,,79,,79,79,,79,79,79,,79,79,,,,79,79,80,80,79,,80,79,80,,,,', +',,79,,,,,,79,,,80,,,,,,80,,80,,80,80,,80,80,80,,80,80,,,,80,80,81,81', +'80,,81,80,81,,,,,,,80,,,,,,80,,,81,,,,,,81,,81,,81,81,,81,81,81,,81', +'81,,,,81,81,82,82,81,,82,81,82,,,,,,,81,,,,,,81,,,82,,,,,,82,,82,,82', +'82,,82,82,82,,82,82,,,,82,82,83,83,82,,83,82,83,,,,,,,82,,,,,,82,,,83', +',,,,,83,,83,,83,83,,83,83,83,,83,83,,,,83,83,84,84,83,,84,83,84,,,,', +',,83,,,,,,83,,,84,,,,,,84,,84,,84,84,,84,84,84,,84,84,,,,84,84,85,85', +'84,,85,84,85,,,,,,,84,,,,,,84,,,85,,,,,,85,,85,,85,85,,85,85,85,,85', +'85,,,,85,85,86,86,85,,86,85,86,,,,,,,85,,,,,,85,,,86,,,,,,86,,86,,86', +'86,,86,86,86,,86,86,,,,86,86,87,87,86,,87,86,87,,,,,,,86,,,,,,86,,,87', +',,,,,87,,87,,87,87,,87,87,87,,87,87,,,,87,87,88,88,87,,88,87,88,,,,', +',,87,,,,,,87,,,88,,,,,,88,,88,,88,88,,88,88,88,,88,88,,,,88,88,68,68', +'88,,68,88,68,,,,,,,88,,,,,,88,,,68,,,,,,68,,68,,68,68,,68,68,68,,68', +'68,,,,68,68,261,261,68,,261,68,261,,,,,,,68,,,,,,68,,,261,,,,,,261,', +'261,,261,261,,261,261,261,,261,261,,,,261,261,91,91,261,,91,261,91,', +',,,,,261,,,,,,261,,,91,,,,,,91,91,91,91,91,91,91,91,91,91,,91,91,,,', +'91,91,91,91,91,254,254,91,,254,,254,,,,91,,,,,91,91,,,,,,254,,,,,,254', +',254,,254,254,,254,254,254,,254,254,,,,254,254,93,93,254,,93,254,93', +',,,,,,254,,,,,,254,,,93,,,,,,93,,93,,93,93,,93,93,93,,93,93,,,,93,93', +'94,94,93,,94,93,94,,,,,,,93,,,,,,93,,,94,,,,,,94,,94,,94,94,,94,94,94', +',94,94,,,,94,94,240,240,94,,240,94,240,,,,,,,94,,,,,,94,,,240,,,,,,240', +',240,,240,240,,240,240,240,,240,240,,,,240,240,239,239,240,,239,240', +'239,,,,,,,240,,,,,,240,,,239,,,,,,239,,239,,239,239,,239,239,239,,239', +'239,,,,239,239,236,236,239,,236,239,236,,,,,,,239,,,,,,239,,,236,,,', +',,236,,236,,236,236,,236,236,236,,236,236,,,,236,236,67,67,236,,67,236', +'67,,,,,,,236,,,,,,236,,,67,,,,,,67,,67,,67,67,,67,67,67,,67,67,67,,', +'67,67,99,99,67,,99,67,99,,,,,,,67,,,,,,67,,,99,99,,,,,99,,99,,99,99', +',99,99,99,,99,99,,,,99,99,230,230,99,,230,99,230,,,,,,,99,,,,,,99,,', +'230,,,,,,230,,230,,230,230,,230,230,230,,230,230,,,,230,230,229,229', +'230,,229,230,229,,,,,,,230,,,,,,230,,,229,,,,,,229,,229,,229,229,,229', +'229,229,,229,229,,,,229,229,103,103,229,,103,229,103,,,,,,,229,,,,,', +'229,,,103,103,,,,,103,,103,,103,103,,103,103,103,,103,103,,,,103,103', +'66,66,103,,66,103,66,,,,,,,103,,,,,,103,,,66,,,,,,66,,66,,66,66,,66', +'66,66,,66,66,66,,,66,66,65,65,66,,65,66,65,,,,,,,66,,,,,,66,,,65,,,', +',,65,,65,,65,65,,65,65,65,,65,65,65,,,65,65,64,64,65,,64,65,64,,,,,', +',65,,,,,,65,,,64,,,,,,64,,64,,64,64,,64,64,64,,64,64,64,,,64,64,63,63', +'64,,63,64,63,,,,,,,64,,,,,,64,,,63,,,,,,63,,63,,63,63,,63,63,63,,63', +'63,63,,,63,63,109,109,63,,109,63,109,,,,,,,63,,,,,,63,,,109,,,,,,109', +',109,,109,109,,109,109,109,,109,109,,,,109,109,227,227,109,,227,109', +'227,,,,,,,109,,,,,,109,,,227,,,,,,227,,227,,227,227,,227,227,227,,227', +'227,,,,227,227,222,222,227,,222,227,222,,,,,,,227,,,,,,227,,,222,,,', +',,222,,222,,222,222,,222,222,222,,222,222,,,,222,222,,,222,218,218,222', +',218,218,218,,,,222,,,,,,222,,,,,,218,,,,,,218,,218,,218,218,,218,218', +'218,,218,218,,,,218,218,279,279,218,,279,218,279,279,,,,,,218,,,,,,218', +',,279,,,,,,279,,279,,279,279,,279,279,279,,279,279,279,,,279,279,69', +'69,279,,69,279,69,,,,,,,279,,,,,,279,,,69,,,,,,69,,69,,69,69,,69,69', +'69,,69,69,,,,69,69,207,207,69,,207,69,207,,,,,,,69,,,,,,69,,,207,,,', +',,207,,207,,207,207,,207,207,207,,207,207,,,,207,207,206,206,207,,206', +'207,206,206,,,,,,207,,,,,,207,,,206,,,,,,206,,206,,206,206,,206,206', +'206,,206,206,206,,,206,206,61,61,206,,61,206,61,,,,,,,206,,,,,,206,', +',61,,,,,,61,,61,,61,61,,61,61,61,,61,61,61,,,61,61,164,164,61,,164,61', +'164,,,,,,,61,,,,,,61,,,164,,,,,,164,,164,,164,164,,164,164,164,,164', +'164,,,,164,164,198,198,164,,198,164,198,198,,,,,,164,,,,,,164,,,198', +',,,,,198,,198,,198,198,,198,198,198,,198,198,198,,,198,198,52,52,198', +',52,198,52,,,,,,,198,,,,,,198,,,52,,,,,,52,,52,,52,52,,52,52,52,,52', +'52,,,,52,52,169,169,52,,169,52,169,,,,,,,52,,,,,,52,,,169,,,,,,169,', +'169,,169,169,,169,169,169,,169,169,,,,169,169,,,169,47,47,169,,47,47', +'47,,,,169,,,,,,169,,,,,,47,,,,,,47,,47,,47,47,,47,47,47,,47,47,,,,47', +'47,290,290,47,,290,47,290,,,,,,,47,,,,,,47,,,290,,,,,,290,,290,,290', +'290,,290,290,290,,290,290,,,,290,290,168,168,290,,168,290,168,,,,,,', +'290,,,,,,290,,,168,,,,,,168,,168,,168,168,,168,168,168,,168,168,,,,168', +'168,167,167,168,,167,168,167,,,,,,,168,,,,,,168,,,167,,,,,,167,,167', +',167,167,,167,167,167,,167,167,,,,167,167,41,41,167,,41,167,41,,,,,', +',167,,,,,,167,,,41,,,,,,41,,41,,41,41,,41,41,41,,41,41,,,,41,41,40,40', +'41,,40,41,40,,,,,,,41,,,,,,41,,,40,,,,,,40,,40,,40,40,,40,40,40,,40', +'40,,,,40,40,39,39,40,,39,40,39,,,,,,,40,,,,,,40,,,39,,,,,,39,,39,,39', +'39,,39,39,39,,39,39,,,,39,39,38,38,39,,38,39,38,,,,,,,39,,,,,,39,,,38', +',,,,,38,,38,,38,38,,38,38,38,,38,38,,,,38,38,306,306,38,,306,38,306', +',,,,,,38,,,,,,38,,,306,,,,,,306,,306,,306,306,,306,306,306,,306,306', +',,,306,306,318,318,306,,318,306,318,318,,,,,,306,,,,,,306,,,318,,,,', +',318,,318,,318,318,,318,318,318,,318,318,318,,,318,318,13,13,318,,13', +'318,13,,,,,,,318,,,,,,318,,,13,,,,,,13,,13,,13,13,,13,13,13,,13,13,', +',,13,13,12,12,13,,12,13,12,,,,,,,13,,,,,,13,,,12,,,,,,12,,12,,12,12', +',12,12,12,,12,12,,,,12,12,11,11,12,,11,12,11,,,,,,,12,,,,,,12,,,11,', +',,,,11,,11,,11,11,,11,11,11,,11,11,,,,11,11,334,334,11,,334,11,334,334', +',,,,,11,,,,,,11,,,334,,,,,,334,,334,,334,334,,334,334,334,,334,334,334', +',,334,334,336,336,334,,336,334,336,336,,,,,,334,,,,,,334,,,336,,,,,', +'336,,336,,336,336,,336,336,336,,336,336,336,,,336,336,4,4,336,,4,336', +'4,,,,,,,336,,,,,,336,,,4,,,,,,4,,4,,4,4,,4,4,4,4,4,4,4,,,4,4,337,337', +'4,,337,4,337,337,,,,,,4,,,,,,4,,,337,,,,,,337,,337,,337,337,,337,337', +'337,,337,337,337,,,337,337,0,0,337,,0,337,0,,,,,,,337,,,,,,337,,,0,', +',,,,0,,0,,0,0,,0,0,0,,0,0,0,,,0,0,205,205,0,,205,0,205,205,,,,,,0,,', +',,,0,,,205,,,,,,205,,205,,205,205,,205,205,205,,205,205,205,,,205,205', +',,205,,,205,,,,233,233,233,233,205,233,233,233,233,233,205,233,233,', +',,,,233,233,233,189,189,189,189,,189,189,189,189,189,,189,189,,,233', +'233,,189,189,189,238,238,238,238,,238,238,238,238,238,,238,238,,,189', +'189,,238,238,238,,,,,,,,,,,,,,,,238,238' ] + racc_action_check = arr = ::Array.new(4874, nil) idx = 0 clist.each do |str| str.split(',', -1).each do |i| @@ -353,225 +353,228 @@ clist = [ end racc_action_pointer = [ - 4621, 340, nil, nil, 4529, 327, nil, 219, nil, nil, - 247, 4391, 4345, 4299, nil, nil, nil, nil, nil, nil, + 4691, 297, nil, nil, 4599, 284, nil, 145, nil, nil, + 285, 4461, 4415, 4369, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, 309, 115, 288, 4161, 4115, - 4069, 4023, -6, 262, 95, 208, nil, 3839, 135, nil, - nil, nil, 3744, nil, nil, nil, nil, nil, nil, nil, - 285, 3606, 267, 3189, 3143, 3097, 3051, 2775, 2358, 3468, - 5, 16, 1576, 1622, 1668, 1714, 1760, 1806, 1852, 1898, - 1944, 1990, 2036, 2082, 2128, 2174, 2220, 4667, 2312, 1386, - 154, 2450, 165, 2545, 2591, 213, 43, 112, 1327, 2821, - nil, nil, nil, 47, 149, -31, 3005, 1275, nil, 1214, - 1153, 1092, 3235, 286, nil, nil, nil, 290, -8, nil, - nil, nil, nil, nil, 1031, 19, nil, 99, nil, nil, - 98, 979, nil, 219, nil, 215, nil, nil, nil, nil, - nil, 23, 156, nil, nil, nil, nil, 771, 701, 427, - 212, 122, 527, 503, 475, 375, 399, 347, 323, 271, - 186, 142, 98, 46, -6, nil, nil, 2266, nil, 579, - 3977, 3931, 3790, 268, 281, nil, nil, 5, nil, -10, - 25, 54, 48, 187, 29, 28, nil, nil, nil, nil, - nil, nil, 4694, 875, 242, nil, 264, nil, 255, 71, - 220, nil, nil, 3698, nil, 137, nil, 117, 2, nil, - 3652, 3560, 3514, 1435, 214, 280, nil, 0, 330, 309, - 68, nil, 219, 3376, nil, nil, 83, 3327, nil, nil, - nil, nil, 3281, nil, 2959, 2913, 111, nil, 4715, nil, - 36, 2867, 72, 4736, 2729, 2683, 124, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, 2637, - 142, nil, nil, 2499, 171, nil, 117, 153, 2404, nil, - 197, 26, 211, 194, 12, 1530, nil, 197, 232, 240, - 243, nil, 49, nil, 247, 1484, 3422, nil, nil, 927, - nil, nil, nil, 823, nil, 753, 279, 3885, 286, nil, - nil, nil, nil, 683, 631, 300, 28, 302, nil, nil, - nil, nil, 451, 145, nil, 4207, -2, 287, nil, 311, - 312, nil, nil, nil, 313, 323, nil, 4253, nil, nil, - nil, 309, 326, nil, 327, nil, nil, nil, nil, nil, - nil, nil, nil, nil, 4437, nil, 4483, 4575, nil, nil, - 334, nil, nil, nil, nil, 338, nil, 339, nil, 341, + nil, nil, nil, nil, nil, 263, 200, 242, 4231, 4185, + 4139, 4093, 85, 219, 118, 120, nil, 3909, 202, nil, + nil, nil, 3814, nil, nil, nil, nil, nil, nil, nil, + 251, 3676, 238, 3259, 3213, 3167, 3121, 2891, 2474, 3538, + 86, 26, 1692, 1738, 1784, 1830, 1876, 1922, 1968, 2014, + 2060, 2106, 2152, 2198, 2244, 2290, 2336, 2382, 2428, 1505, + 164, 2566, 174, 2661, 2707, 196, 64, 170, 1468, 2937, + nil, 253, -28, 3075, 1409, nil, 1350, 1291, 1232, 3305, + 21, nil, nil, nil, 67, -11, nil, nil, nil, nil, + nil, 1173, 138, nil, 161, nil, nil, 219, 1114, nil, + 26, nil, 236, nil, nil, nil, nil, nil, -5, 13, + nil, nil, nil, nil, 878, 795, 250, 77, 18, 594, + 570, 528, 445, 421, 377, 353, 309, 226, 169, 112, + 53, -6, nil, nil, 3722, nil, 653, 4047, 4001, 3860, + 255, 255, nil, nil, 7, nil, -7, 22, 119, 109, + 113, 53, 24, nil, nil, nil, nil, nil, nil, 4785, + 996, 218, nil, 238, nil, 246, 189, nil, 3768, nil, + 227, nil, 216, -9, nil, 4737, 3630, 3584, 1551, 176, + 127, nil, 27, 143, 109, 24, nil, 53, 3446, nil, + nil, 197, 3397, nil, nil, nil, nil, 3351, nil, 3029, + 2983, 2, nil, 4764, nil, 81, 2845, 102, 4806, 2799, + 2753, 168, nil, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, 2615, 158, nil, 175, nil, 126, + 167, 2520, nil, 203, 101, 211, 196, 74, 1646, nil, + 193, 222, 228, 230, nil, 107, nil, 234, 1600, 3492, + nil, nil, 1055, nil, nil, nil, 937, nil, 854, 250, + 3955, -4, nil, nil, nil, nil, 771, 712, 255, 197, + nil, nil, nil, 504, 146, nil, 4277, 264, 243, nil, + 267, 268, nil, nil, nil, 268, 275, nil, 4323, nil, + nil, nil, 263, 280, nil, 281, nil, nil, nil, nil, + nil, nil, nil, nil, 4507, nil, 4553, 4645, nil, nil, + 289, nil, nil, nil, nil, 296, nil, 298, nil, 308, nil, nil, nil, nil, nil ] racc_action_default = [ - -209, -210, -1, -2, -3, -4, -7, -9, -10, -15, - -109, -210, -210, -210, -43, -44, -45, -46, -47, -48, + -203, -204, -1, -2, -3, -4, -7, -9, -10, -15, + -103, -204, -204, -204, -43, -44, -45, -46, -47, -48, -49, -50, -51, -52, -53, -54, -55, -56, -57, -58, - -59, -60, -61, -62, -63, -68, -69, -73, -210, -210, - -210, -210, -210, -119, -210, -210, -164, -210, -210, -174, - -175, -176, -210, -178, -185, -186, -187, -188, -189, -190, - -210, -210, -6, -210, -210, -210, -210, -210, -210, -210, - -210, -210, -210, -210, -210, -210, -210, -210, -210, -210, - -210, -210, -210, -210, -210, -210, -210, -210, -210, -210, - -210, -127, -122, -209, -209, -27, -210, -34, -210, -210, - -70, -75, -76, -209, -210, -210, -210, -210, -86, -210, - -210, -210, -210, -209, -153, -154, -120, -209, -209, -145, - -147, -148, -149, -150, -41, -210, -167, -210, -170, -171, - -210, -182, -177, -210, 365, -5, -8, -11, -12, -13, - -14, -210, -17, -18, -162, -163, -19, -20, -21, -22, - -23, -24, -25, -26, -28, -29, -30, -31, -32, -33, - -35, -36, -37, -38, -210, -39, -104, -210, -74, -210, - -202, -208, -196, -193, -191, -117, -128, -185, -131, -189, - -210, -199, -197, -205, -187, -188, -195, -200, -201, -203, - -204, -206, -127, -126, -210, -125, -210, -40, -191, -65, - -210, -80, -81, -210, -84, -191, -158, -161, -210, -72, - -210, -210, -210, -127, -193, -209, -155, -210, -210, -210, - -210, -151, -210, -210, -165, -168, -210, -210, -179, -180, - -181, -183, -210, -16, -210, -210, -191, -106, -127, -116, - -210, -194, -210, -192, -210, -210, -191, -130, -132, -196, - -197, -198, -199, -202, -205, -207, -208, -123, -124, -192, - -210, -67, -77, -210, -210, -83, -210, -192, -210, -71, - -210, -89, -210, -95, -210, -210, -99, -193, -191, -210, - -210, -139, -210, -156, -191, -210, -210, -146, -152, -42, - -166, -169, -172, -173, -184, -108, -210, -192, -191, -112, - -118, -113, -129, -133, -134, -210, -64, -210, -79, -82, - -85, -159, -160, -89, -88, -210, -210, -95, -94, -210, - -210, -103, -98, -100, -210, -210, -114, -210, -140, -141, - -142, -210, -210, -136, -210, -144, -105, -107, -115, -121, - -66, -78, -87, -90, -210, -93, -210, -210, -110, -111, - -210, -138, -157, -135, -143, -210, -92, -210, -97, -210, - -102, -137, -91, -96, -101 ] + -59, -60, -61, -62, -63, -68, -69, -73, -204, -204, + -204, -204, -204, -113, -204, -204, -158, -204, -204, -168, + -169, -170, -204, -172, -179, -180, -181, -182, -183, -184, + -204, -204, -6, -204, -204, -204, -204, -204, -204, -204, + -204, -204, -204, -204, -204, -204, -204, -204, -204, -204, + -204, -204, -204, -204, -204, -204, -204, -204, -204, -204, + -204, -121, -116, -203, -203, -27, -204, -34, -204, -204, + -70, -204, -204, -204, -204, -80, -204, -204, -204, -204, + -203, -147, -148, -114, -203, -203, -139, -141, -142, -143, + -144, -41, -204, -161, -204, -164, -165, -204, -176, -171, + -204, 355, -5, -8, -11, -12, -13, -14, -204, -17, + -18, -156, -157, -19, -20, -21, -22, -23, -24, -25, + -26, -28, -29, -30, -31, -32, -33, -35, -36, -37, + -38, -204, -39, -98, -204, -74, -204, -196, -202, -190, + -187, -185, -111, -122, -179, -125, -183, -204, -193, -191, + -199, -181, -182, -189, -194, -195, -197, -198, -200, -121, + -120, -204, -119, -204, -40, -185, -65, -75, -204, -78, + -185, -152, -155, -204, -72, -204, -204, -204, -121, -187, + -203, -149, -204, -204, -204, -204, -145, -204, -204, -159, + -162, -204, -204, -173, -174, -175, -177, -204, -16, -204, + -204, -185, -100, -121, -110, -204, -188, -204, -186, -204, + -204, -185, -124, -126, -190, -191, -192, -193, -196, -199, + -201, -202, -117, -118, -186, -204, -67, -204, -77, -204, + -186, -204, -71, -204, -83, -204, -89, -204, -204, -93, + -187, -185, -204, -204, -133, -204, -150, -185, -204, -204, + -140, -146, -42, -160, -163, -166, -167, -178, -102, -204, + -186, -185, -106, -112, -107, -123, -127, -128, -204, -64, + -76, -79, -153, -154, -83, -82, -204, -204, -89, -88, + -204, -204, -97, -92, -94, -204, -204, -108, -204, -134, + -135, -136, -204, -204, -130, -204, -138, -99, -101, -109, + -115, -66, -81, -84, -204, -87, -204, -204, -104, -105, + -204, -132, -151, -129, -137, -204, -86, -204, -91, -204, + -96, -131, -85, -90, -95 ] racc_goto_table = [ - 2, 117, 100, 95, 97, 98, 3, 132, 129, 166, - 174, 240, 130, 205, 314, 318, 123, 121, 215, 173, - 1, 276, 218, 320, 287, 62, 288, 242, 194, 196, - 107, 109, 110, 111, 145, 145, 125, 143, 146, 124, - 214, 144, 144, 236, 131, 137, 138, 139, 140, 275, - 300, 260, 279, 238, 343, 302, 342, 141, 266, 345, - 124, 142, 262, 200, 147, 148, 149, 150, 151, 152, - 153, 154, 155, 156, 157, 158, 159, 160, 161, 162, - 163, 164, 135, 169, 323, 193, 193, 237, 198, 296, - 280, 124, 328, 219, 203, 208, 311, 127, 124, 305, - 165, 136, 231, 232, 169, 230, nil, nil, nil, 201, - nil, 246, nil, nil, nil, 324, nil, nil, nil, 216, - nil, nil, nil, 216, 221, 284, nil, nil, nil, nil, - nil, 325, 278, nil, nil, nil, nil, 331, 117, nil, - nil, 277, nil, nil, nil, nil, nil, nil, nil, nil, - nil, 338, nil, nil, 123, 121, nil, 298, nil, 164, - nil, nil, 107, 109, 110, 261, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, 292, 294, nil, nil, - 130, 123, 121, 123, 121, nil, nil, nil, nil, nil, - nil, nil, nil, 264, 124, 169, nil, nil, nil, nil, - 270, 272, nil, nil, nil, 289, nil, 337, nil, 293, - nil, 281, nil, nil, 131, nil, 289, 295, nil, nil, - nil, nil, nil, 169, nil, nil, 303, 304, nil, 329, - nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, 289, nil, nil, nil, nil, nil, nil, nil, nil, - 312, nil, nil, 307, nil, nil, nil, 124, nil, nil, - nil, nil, 340, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, 332, 334, nil, nil, 164, + 2, 3, 100, 95, 97, 98, 114, 163, 129, 126, + 170, 171, 127, 200, 118, 305, 309, 120, 235, 210, + 280, 311, 281, 213, 237, 231, 269, 293, 209, 233, + 104, 106, 107, 108, 142, 142, 62, 140, 143, 121, + 191, 193, 141, 141, 128, 268, 295, 333, 255, 134, + 135, 136, 137, 259, 197, 332, 273, 272, 335, 319, + 121, 139, 214, 162, 144, 145, 146, 147, 148, 149, + 150, 151, 152, 153, 154, 155, 156, 157, 158, 159, + 160, 161, 232, 166, 289, 190, 190, 314, 302, 122, + 124, 121, 133, 132, 298, 121, 1, 226, 227, 225, + nil, 166, nil, nil, nil, nil, nil, nil, nil, 241, + 138, 211, nil, nil, nil, 211, 216, nil, 315, nil, + nil, nil, nil, 277, 316, nil, nil, 270, 271, nil, + 322, nil, nil, nil, nil, nil, nil, nil, nil, nil, + 114, 195, nil, nil, 329, 203, nil, nil, nil, 118, + nil, nil, 120, 291, nil, nil, 161, nil, nil, 104, + 106, 107, 256, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, 107, nil, nil, + nil, nil, 285, 287, 118, 127, 118, 120, nil, 120, + nil, nil, nil, nil, nil, nil, nil, nil, 257, 121, + 166, nil, nil, nil, nil, 263, 265, nil, 328, nil, + 282, 274, nil, nil, 286, nil, nil, nil, nil, 128, + nil, 282, 288, nil, nil, nil, nil, nil, 166, nil, + nil, 296, 297, nil, nil, nil, nil, 320, nil, nil, + nil, nil, nil, nil, nil, nil, 282, nil, nil, nil, + nil, nil, nil, 303, nil, nil, nil, nil, nil, nil, + 121, nil, nil, nil, nil, 331, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, 323, 325, + nil, nil, 161, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, 104, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, 350, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, 340, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, 355, nil, 357, 359 ] + nil, nil, nil, nil, 345, nil, 347, 349 ] racc_goto_check = [ - 2, 65, 37, 9, 9, 9, 3, 78, 74, 52, - 57, 56, 31, 45, 47, 48, 30, 35, 66, 55, - 1, 50, 66, 51, 71, 5, 71, 36, 61, 61, - 9, 9, 9, 9, 31, 31, 11, 12, 12, 9, - 55, 30, 30, 53, 9, 7, 7, 7, 7, 49, - 58, 36, 56, 59, 46, 62, 47, 11, 36, 48, - 9, 9, 44, 43, 9, 9, 9, 9, 9, 9, + 2, 3, 37, 9, 9, 9, 62, 49, 75, 71, + 52, 54, 31, 42, 35, 44, 45, 30, 53, 63, + 68, 48, 68, 63, 36, 50, 47, 55, 52, 56, + 9, 9, 9, 9, 31, 31, 5, 12, 12, 9, + 58, 58, 30, 30, 9, 46, 59, 43, 36, 7, + 7, 7, 7, 36, 41, 44, 64, 53, 45, 65, + 9, 9, 67, 13, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, - 9, 9, 5, 9, 50, 9, 9, 52, 11, 36, - 67, 9, 68, 70, 42, 11, 72, 73, 9, 36, - 13, 6, 79, 80, 9, 82, nil, nil, nil, 3, - nil, 57, nil, nil, nil, 56, nil, nil, nil, 3, - nil, nil, nil, 3, 3, 45, nil, nil, nil, nil, - nil, 36, 57, nil, nil, nil, nil, 36, 65, nil, - nil, 55, nil, nil, nil, nil, nil, nil, nil, nil, - nil, 36, nil, nil, 30, 35, nil, 57, nil, 9, - nil, nil, 9, 9, 9, 37, nil, nil, nil, nil, + 9, 9, 49, 9, 36, 9, 9, 47, 69, 11, + 70, 9, 6, 5, 36, 9, 1, 76, 77, 79, + nil, 9, nil, nil, nil, nil, nil, nil, nil, 54, + 11, 3, nil, nil, nil, 3, 3, nil, 53, nil, + nil, nil, nil, 42, 36, nil, nil, 52, 54, nil, + 36, nil, nil, nil, nil, nil, nil, nil, nil, nil, + 62, 11, nil, nil, 36, 11, nil, nil, nil, 35, + nil, nil, 30, 54, nil, nil, 9, nil, nil, 9, + 9, 9, 37, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, 74, 78, nil, nil, - 31, 30, 35, 30, 35, nil, nil, nil, nil, nil, - nil, nil, nil, 2, 9, 9, nil, nil, nil, nil, - 2, 2, nil, nil, nil, 9, nil, 52, nil, 9, - nil, 3, nil, nil, 9, nil, 9, 9, nil, nil, - nil, nil, nil, 9, nil, nil, 9, 9, nil, 65, + nil, nil, 71, 75, 35, 31, 35, 30, nil, 30, + nil, nil, nil, nil, nil, nil, nil, nil, 2, 9, + 9, nil, nil, nil, nil, 2, 2, nil, 49, nil, + 9, 3, nil, nil, 9, nil, nil, nil, nil, 9, + nil, 9, 9, nil, nil, nil, nil, nil, 9, nil, + nil, 9, 9, nil, nil, nil, nil, 62, nil, nil, + nil, nil, nil, nil, nil, nil, 9, nil, nil, nil, + nil, nil, nil, 9, nil, nil, nil, nil, nil, nil, + 9, nil, nil, nil, nil, 37, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, 2, 2, + nil, nil, 9, nil, nil, nil, nil, nil, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, 9, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, 9, nil, nil, nil, nil, nil, nil, nil, nil, - 9, nil, nil, 2, nil, nil, nil, 9, nil, nil, - nil, nil, 37, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, 2, 2, nil, nil, 9, - nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, 9, nil, nil, - nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - nil, nil, nil, nil, nil, nil, nil, 2, nil, nil, + nil, nil, nil, nil, nil, nil, nil, nil, 2, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, 2, nil, 2, 2 ] racc_goto_pointer = [ - nil, 20, 0, 6, nil, 21, 38, -19, nil, -8, - nil, -11, -33, 11, nil, nil, nil, nil, nil, nil, + nil, 96, 0, 1, nil, 32, 29, -15, nil, -8, + nil, 42, -33, -26, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, nil, - -29, -36, nil, nil, nil, -28, -147, -34, nil, nil, - nil, nil, -10, -40, -138, -92, -261, -257, -258, -163, - -191, -251, -80, -124, nil, -72, -162, -81, -191, -116, - nil, -65, -188, nil, nil, -43, -95, -125, -190, nil, - -25, -196, -171, 49, -40, nil, nil, nil, -45, -31, - -30, nil, -28 ] + -28, -36, nil, nil, nil, -31, -147, -34, nil, nil, + nil, -47, -89, -259, -249, -250, -162, -181, -246, -82, + -139, nil, -81, -152, -80, -209, -137, nil, -53, -192, + nil, nil, -38, -91, -154, -216, nil, -53, -195, -172, + 42, -39, nil, nil, nil, -44, -33, -32, nil, -31 ] racc_goto_default = [ - nil, nil, nil, 195, 4, 5, 6, 7, 8, 10, - 9, 274, nil, nil, 14, 35, 15, 16, 17, 18, + nil, nil, nil, 192, 4, 5, 6, 7, 8, 10, + 9, 267, nil, nil, 14, 35, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, nil, nil, 36, 37, - 101, 102, 103, nil, nil, nil, 108, nil, nil, nil, - nil, nil, nil, nil, 41, nil, nil, nil, 175, nil, - 92, nil, 176, 180, 178, 113, nil, nil, nil, 118, - nil, 119, 206, nil, nil, 49, 50, 52, nil, nil, - nil, 133, nil ] + 101, nil, nil, 105, nil, nil, nil, nil, nil, nil, + nil, 41, nil, nil, nil, 172, nil, 92, nil, 173, + 177, 175, 110, nil, nil, nil, 115, nil, 116, 201, + nil, nil, 49, 50, 52, nil, nil, nil, 130, nil ] racc_reduce_table = [ 0, 0, :racc_error, - 1, 78, :_reduce_1, - 1, 78, :_reduce_none, - 1, 79, :_reduce_3, - 1, 81, :_reduce_4, - 3, 81, :_reduce_5, - 2, 81, :_reduce_6, - 1, 82, :_reduce_7, - 3, 82, :_reduce_8, - 1, 83, :_reduce_none, - 1, 84, :_reduce_10, - 3, 84, :_reduce_11, - 3, 84, :_reduce_12, - 3, 84, :_reduce_13, - 3, 84, :_reduce_14, + 1, 77, :_reduce_1, + 1, 77, :_reduce_none, + 1, 78, :_reduce_3, + 1, 80, :_reduce_4, + 3, 80, :_reduce_5, + 2, 80, :_reduce_6, + 1, 81, :_reduce_7, + 3, 81, :_reduce_8, + 1, 82, :_reduce_none, + 1, 83, :_reduce_10, + 3, 83, :_reduce_11, + 3, 83, :_reduce_12, + 3, 83, :_reduce_13, + 3, 83, :_reduce_14, + 1, 85, :_reduce_none, + 4, 85, :_reduce_16, + 3, 85, :_reduce_17, + 3, 85, :_reduce_18, + 3, 85, :_reduce_19, + 3, 85, :_reduce_20, + 3, 85, :_reduce_21, + 3, 85, :_reduce_22, + 3, 85, :_reduce_23, + 3, 85, :_reduce_24, + 3, 85, :_reduce_25, + 3, 85, :_reduce_26, + 2, 85, :_reduce_27, + 3, 85, :_reduce_28, + 3, 85, :_reduce_29, + 3, 85, :_reduce_30, + 3, 85, :_reduce_31, + 3, 85, :_reduce_32, + 3, 85, :_reduce_33, + 2, 85, :_reduce_34, + 3, 85, :_reduce_35, + 3, 85, :_reduce_36, + 3, 85, :_reduce_37, + 3, 85, :_reduce_38, + 3, 85, :_reduce_39, + 3, 85, :_reduce_40, + 1, 87, :_reduce_41, + 3, 87, :_reduce_42, 1, 86, :_reduce_none, - 4, 86, :_reduce_16, - 3, 86, :_reduce_17, - 3, 86, :_reduce_18, - 3, 86, :_reduce_19, - 3, 86, :_reduce_20, - 3, 86, :_reduce_21, - 3, 86, :_reduce_22, - 3, 86, :_reduce_23, - 3, 86, :_reduce_24, - 3, 86, :_reduce_25, - 3, 86, :_reduce_26, - 2, 86, :_reduce_27, - 3, 86, :_reduce_28, - 3, 86, :_reduce_29, - 3, 86, :_reduce_30, - 3, 86, :_reduce_31, - 3, 86, :_reduce_32, - 3, 86, :_reduce_33, - 2, 86, :_reduce_34, - 3, 86, :_reduce_35, - 3, 86, :_reduce_36, - 3, 86, :_reduce_37, - 3, 86, :_reduce_38, - 3, 86, :_reduce_39, - 3, 86, :_reduce_40, - 1, 88, :_reduce_41, - 3, 88, :_reduce_42, - 1, 87, :_reduce_none, - 1, 92, :_reduce_none, + 1, 91, :_reduce_none, + 1, 91, :_reduce_none, + 1, 91, :_reduce_none, + 1, 91, :_reduce_none, + 1, 91, :_reduce_none, + 1, 91, :_reduce_none, + 1, 91, :_reduce_none, + 1, 91, :_reduce_none, + 1, 91, :_reduce_none, + 1, 91, :_reduce_none, 1, 92, :_reduce_none, 1, 92, :_reduce_none, 1, 92, :_reduce_none, @@ -580,167 +583,152 @@ racc_reduce_table = [ 1, 92, :_reduce_none, 1, 92, :_reduce_none, 1, 92, :_reduce_none, - 1, 92, :_reduce_none, - 1, 93, :_reduce_none, - 1, 93, :_reduce_none, - 1, 93, :_reduce_none, - 1, 93, :_reduce_none, - 1, 93, :_reduce_none, - 1, 93, :_reduce_none, - 1, 93, :_reduce_none, - 1, 93, :_reduce_none, - 1, 108, :_reduce_62, - 1, 108, :_reduce_63, - 5, 91, :_reduce_64, - 3, 91, :_reduce_65, - 6, 91, :_reduce_66, - 4, 91, :_reduce_67, - 1, 91, :_reduce_68, - 1, 95, :_reduce_69, - 2, 95, :_reduce_70, - 4, 115, :_reduce_71, - 3, 115, :_reduce_72, - 1, 115, :_reduce_73, - 3, 116, :_reduce_74, - 1, 114, :_reduce_none, - 1, 114, :_reduce_none, - 3, 117, :_reduce_77, - 3, 121, :_reduce_78, - 2, 121, :_reduce_79, - 1, 120, :_reduce_none, - 1, 120, :_reduce_none, - 4, 118, :_reduce_82, - 3, 118, :_reduce_83, - 2, 119, :_reduce_84, - 4, 119, :_reduce_85, - 2, 98, :_reduce_86, - 5, 123, :_reduce_87, - 4, 123, :_reduce_88, - 0, 124, :_reduce_none, - 2, 124, :_reduce_90, - 4, 124, :_reduce_91, - 3, 124, :_reduce_92, - 6, 99, :_reduce_93, - 5, 99, :_reduce_94, - 0, 125, :_reduce_none, - 4, 125, :_reduce_96, - 3, 125, :_reduce_97, - 5, 97, :_reduce_98, - 1, 126, :_reduce_99, - 2, 126, :_reduce_100, - 5, 127, :_reduce_101, - 4, 127, :_reduce_102, - 1, 128, :_reduce_103, - 1, 90, :_reduce_none, - 4, 90, :_reduce_105, - 1, 130, :_reduce_106, - 3, 130, :_reduce_107, - 3, 129, :_reduce_108, - 1, 85, :_reduce_109, - 6, 85, :_reduce_110, - 6, 85, :_reduce_111, - 5, 85, :_reduce_112, - 5, 85, :_reduce_113, - 5, 85, :_reduce_114, - 4, 135, :_reduce_115, - 1, 136, :_reduce_116, - 1, 132, :_reduce_117, - 3, 132, :_reduce_118, - 1, 131, :_reduce_119, - 2, 131, :_reduce_120, - 6, 96, :_reduce_121, - 2, 96, :_reduce_122, - 3, 137, :_reduce_123, - 3, 137, :_reduce_124, - 1, 138, :_reduce_none, - 1, 138, :_reduce_none, - 0, 134, :_reduce_127, - 1, 134, :_reduce_128, - 3, 134, :_reduce_129, - 1, 140, :_reduce_none, - 1, 140, :_reduce_none, - 1, 140, :_reduce_none, - 3, 139, :_reduce_133, - 3, 139, :_reduce_134, - 6, 100, :_reduce_135, - 5, 100, :_reduce_136, - 7, 101, :_reduce_137, - 6, 101, :_reduce_138, - 1, 144, :_reduce_none, - 2, 144, :_reduce_140, - 1, 145, :_reduce_none, - 1, 145, :_reduce_none, - 6, 102, :_reduce_143, - 5, 102, :_reduce_144, - 1, 146, :_reduce_145, - 3, 146, :_reduce_146, - 1, 148, :_reduce_147, - 1, 148, :_reduce_148, - 1, 148, :_reduce_149, - 1, 148, :_reduce_none, - 1, 147, :_reduce_none, - 2, 147, :_reduce_152, - 1, 142, :_reduce_153, - 1, 142, :_reduce_154, - 1, 143, :_reduce_155, - 2, 143, :_reduce_156, - 4, 143, :_reduce_157, - 1, 122, :_reduce_158, - 3, 122, :_reduce_159, - 3, 149, :_reduce_160, - 1, 149, :_reduce_161, + 1, 107, :_reduce_62, + 1, 107, :_reduce_63, + 5, 90, :_reduce_64, + 3, 90, :_reduce_65, + 6, 90, :_reduce_66, + 4, 90, :_reduce_67, + 1, 90, :_reduce_68, + 1, 94, :_reduce_69, + 2, 94, :_reduce_70, + 4, 114, :_reduce_71, + 3, 114, :_reduce_72, + 1, 114, :_reduce_73, + 3, 115, :_reduce_74, + 2, 113, :_reduce_75, + 3, 117, :_reduce_76, + 2, 117, :_reduce_77, + 2, 116, :_reduce_78, + 4, 116, :_reduce_79, + 2, 97, :_reduce_80, + 5, 119, :_reduce_81, + 4, 119, :_reduce_82, + 0, 120, :_reduce_none, + 2, 120, :_reduce_84, + 4, 120, :_reduce_85, + 3, 120, :_reduce_86, + 6, 98, :_reduce_87, + 5, 98, :_reduce_88, + 0, 121, :_reduce_none, + 4, 121, :_reduce_90, + 3, 121, :_reduce_91, + 5, 96, :_reduce_92, + 1, 122, :_reduce_93, + 2, 122, :_reduce_94, + 5, 123, :_reduce_95, + 4, 123, :_reduce_96, + 1, 124, :_reduce_97, 1, 89, :_reduce_none, - 1, 89, :_reduce_none, - 1, 94, :_reduce_164, - 3, 103, :_reduce_165, - 4, 103, :_reduce_166, - 2, 103, :_reduce_167, - 3, 106, :_reduce_168, - 4, 106, :_reduce_169, - 2, 106, :_reduce_170, - 1, 150, :_reduce_171, - 3, 150, :_reduce_172, - 3, 151, :_reduce_173, - 1, 112, :_reduce_none, - 1, 112, :_reduce_none, - 1, 152, :_reduce_176, - 2, 153, :_reduce_177, - 1, 154, :_reduce_178, - 1, 156, :_reduce_179, - 1, 157, :_reduce_180, - 2, 155, :_reduce_181, - 1, 158, :_reduce_182, - 1, 159, :_reduce_183, - 2, 159, :_reduce_184, - 1, 111, :_reduce_185, - 1, 109, :_reduce_186, - 1, 110, :_reduce_187, - 1, 105, :_reduce_188, - 1, 104, :_reduce_189, - 1, 107, :_reduce_190, - 0, 113, :_reduce_none, - 1, 113, :_reduce_192, - 0, 133, :_reduce_none, - 1, 133, :_reduce_none, - 1, 141, :_reduce_none, - 1, 141, :_reduce_none, - 1, 141, :_reduce_none, - 1, 141, :_reduce_none, - 1, 141, :_reduce_none, - 1, 141, :_reduce_none, - 1, 141, :_reduce_none, - 1, 141, :_reduce_none, - 1, 141, :_reduce_none, - 1, 141, :_reduce_none, - 1, 141, :_reduce_none, - 1, 141, :_reduce_none, + 4, 89, :_reduce_99, + 1, 126, :_reduce_100, + 3, 126, :_reduce_101, + 3, 125, :_reduce_102, + 1, 84, :_reduce_103, + 6, 84, :_reduce_104, + 6, 84, :_reduce_105, + 5, 84, :_reduce_106, + 5, 84, :_reduce_107, + 5, 84, :_reduce_108, + 4, 131, :_reduce_109, + 1, 132, :_reduce_110, + 1, 128, :_reduce_111, + 3, 128, :_reduce_112, + 1, 127, :_reduce_113, + 2, 127, :_reduce_114, + 6, 95, :_reduce_115, + 2, 95, :_reduce_116, + 3, 133, :_reduce_117, + 3, 133, :_reduce_118, + 1, 134, :_reduce_none, + 1, 134, :_reduce_none, + 0, 130, :_reduce_121, + 1, 130, :_reduce_122, + 3, 130, :_reduce_123, + 1, 136, :_reduce_none, + 1, 136, :_reduce_none, + 1, 136, :_reduce_none, + 3, 135, :_reduce_127, + 3, 135, :_reduce_128, + 6, 99, :_reduce_129, + 5, 99, :_reduce_130, + 7, 100, :_reduce_131, + 6, 100, :_reduce_132, + 1, 140, :_reduce_none, + 2, 140, :_reduce_134, 1, 141, :_reduce_none, 1, 141, :_reduce_none, - 0, 80, :_reduce_209 ] - -racc_reduce_n = 210 - -racc_shift_n = 365 + 6, 101, :_reduce_137, + 5, 101, :_reduce_138, + 1, 142, :_reduce_139, + 3, 142, :_reduce_140, + 1, 144, :_reduce_141, + 1, 144, :_reduce_142, + 1, 144, :_reduce_143, + 1, 144, :_reduce_none, + 1, 143, :_reduce_none, + 2, 143, :_reduce_146, + 1, 138, :_reduce_147, + 1, 138, :_reduce_148, + 1, 139, :_reduce_149, + 2, 139, :_reduce_150, + 4, 139, :_reduce_151, + 1, 118, :_reduce_152, + 3, 118, :_reduce_153, + 3, 145, :_reduce_154, + 1, 145, :_reduce_155, + 1, 88, :_reduce_none, + 1, 88, :_reduce_none, + 1, 93, :_reduce_158, + 3, 102, :_reduce_159, + 4, 102, :_reduce_160, + 2, 102, :_reduce_161, + 3, 105, :_reduce_162, + 4, 105, :_reduce_163, + 2, 105, :_reduce_164, + 1, 146, :_reduce_165, + 3, 146, :_reduce_166, + 3, 147, :_reduce_167, + 1, 111, :_reduce_none, + 1, 111, :_reduce_none, + 1, 148, :_reduce_170, + 2, 149, :_reduce_171, + 1, 150, :_reduce_172, + 1, 152, :_reduce_173, + 1, 153, :_reduce_174, + 2, 151, :_reduce_175, + 1, 154, :_reduce_176, + 1, 155, :_reduce_177, + 2, 155, :_reduce_178, + 1, 110, :_reduce_179, + 1, 108, :_reduce_180, + 1, 109, :_reduce_181, + 1, 104, :_reduce_182, + 1, 103, :_reduce_183, + 1, 106, :_reduce_184, + 0, 112, :_reduce_none, + 1, 112, :_reduce_186, + 0, 129, :_reduce_none, + 1, 129, :_reduce_none, + 1, 137, :_reduce_none, + 1, 137, :_reduce_none, + 1, 137, :_reduce_none, + 1, 137, :_reduce_none, + 1, 137, :_reduce_none, + 1, 137, :_reduce_none, + 1, 137, :_reduce_none, + 1, 137, :_reduce_none, + 1, 137, :_reduce_none, + 1, 137, :_reduce_none, + 1, 137, :_reduce_none, + 1, 137, :_reduce_none, + 1, 137, :_reduce_none, + 1, 137, :_reduce_none, + 0, 79, :_reduce_203 ] + +racc_reduce_n = 204 + +racc_shift_n = 355 racc_token_table = { false => 0, @@ -812,16 +800,15 @@ racc_token_table = { :IN => 66, :UNLESS => 67, :PIPE => 68, - :LAMBDA => 69, - :SELBRACE => 70, - :LOW => 71, - :HIGH => 72, - :CALL => 73, - :MODULO => 74, - :TITLE_COLON => 75, - :CASE_COLON => 76 } + :SELBRACE => 69, + :LOW => 70, + :HIGH => 71, + :CALL => 72, + :MODULO => 73, + :TITLE_COLON => 74, + :CASE_COLON => 75 } -racc_nt_base = 77 +racc_nt_base = 76 racc_use_result_var = true @@ -911,7 +898,6 @@ Racc_token_to_s_table = [ "IN", "UNLESS", "PIPE", - "LAMBDA", "SELBRACE", "LOW", "HIGH", @@ -959,10 +945,7 @@ Racc_token_to_s_table = [ "lambda", "call_method_expression", "named_access", - "lambda_j8", - "lambda_ruby", "lambda_parameter_list", - "optional_farrow", "lambda_rest", "parameters", "if_part", @@ -1429,71 +1412,45 @@ module_eval(<<'.,.,', 'egrammar.ra', 197) end .,., -# reduce 75 omitted - -# reduce 76 omitted - -module_eval(<<'.,.,', 'egrammar.ra', 212) - def _reduce_77(val, _values, result) - result = Factory.LAMBDA(val[0], val[2]) +module_eval(<<'.,.,', 'egrammar.ra', 209) + def _reduce_75(val, _values, result) + result = Factory.LAMBDA(val[0], val[1]) # loc result, val[1] # TODO result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 217) - def _reduce_78(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 214) + def _reduce_76(val, _values, result) result = val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 218) - def _reduce_79(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 215) + def _reduce_77(val, _values, result) result = nil result end .,., -# reduce 80 omitted - -# reduce 81 omitted - -module_eval(<<'.,.,', 'egrammar.ra', 228) - def _reduce_82(val, _values, result) - result = Factory.LAMBDA(val[1], val[2]) - loc result, val[0], val[3] - - result - end -.,., - -module_eval(<<'.,.,', 'egrammar.ra', 232) - def _reduce_83(val, _values, result) - result = Factory.LAMBDA(val[1], nil) - loc result, val[0], val[2] - - result - end -.,., - -module_eval(<<'.,.,', 'egrammar.ra', 238) - def _reduce_84(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 219) + def _reduce_78(val, _values, result) result = [] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 239) - def _reduce_85(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 220) + def _reduce_79(val, _values, result) result = val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 249) - def _reduce_86(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 230) + def _reduce_80(val, _values, result) result = val[1] loc(result, val[0], val[1]) @@ -1501,8 +1458,8 @@ module_eval(<<'.,.,', 'egrammar.ra', 249) end .,., -module_eval(<<'.,.,', 'egrammar.ra', 256) - def _reduce_87(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 237) + def _reduce_81(val, _values, result) result = Factory.IF(val[0], Factory.block_or_expression(*val[2]), val[4]) loc(result, val[0], (val[4] ? val[4] : val[3])) @@ -1510,8 +1467,8 @@ module_eval(<<'.,.,', 'egrammar.ra', 256) end .,., -module_eval(<<'.,.,', 'egrammar.ra', 260) - def _reduce_88(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 241) + def _reduce_82(val, _values, result) result = Factory.IF(val[0], nil, val[3]) loc(result, val[0], (val[3] ? val[3] : val[2])) @@ -1519,10 +1476,10 @@ module_eval(<<'.,.,', 'egrammar.ra', 260) end .,., -# reduce 89 omitted +# reduce 83 omitted -module_eval(<<'.,.,', 'egrammar.ra', 268) - def _reduce_90(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 249) + def _reduce_84(val, _values, result) result = val[1] loc(result, val[0], val[1]) @@ -1530,8 +1487,8 @@ module_eval(<<'.,.,', 'egrammar.ra', 268) end .,., -module_eval(<<'.,.,', 'egrammar.ra', 272) - def _reduce_91(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 253) + def _reduce_85(val, _values, result) result = Factory.block_or_expression(*val[2]) loc result, val[0], val[3] @@ -1539,16 +1496,16 @@ module_eval(<<'.,.,', 'egrammar.ra', 272) end .,., -module_eval(<<'.,.,', 'egrammar.ra', 276) - def _reduce_92(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 257) + def _reduce_86(val, _values, result) result = nil # don't think a nop is needed here either result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 285) - def _reduce_93(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 266) + def _reduce_87(val, _values, result) result = Factory.UNLESS(val[1], Factory.block_or_expression(*val[3]), val[5]) loc result, val[0], val[4] @@ -1556,8 +1513,8 @@ module_eval(<<'.,.,', 'egrammar.ra', 285) end .,., -module_eval(<<'.,.,', 'egrammar.ra', 289) - def _reduce_94(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 270) + def _reduce_88(val, _values, result) result = Factory.UNLESS(val[1], nil, nil) loc result, val[0], val[4] @@ -1565,10 +1522,10 @@ module_eval(<<'.,.,', 'egrammar.ra', 289) end .,., -# reduce 95 omitted +# reduce 89 omitted -module_eval(<<'.,.,', 'egrammar.ra', 299) - def _reduce_96(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 280) + def _reduce_90(val, _values, result) result = Factory.block_or_expression(*val[2]) loc result, val[0], val[3] @@ -1576,16 +1533,16 @@ module_eval(<<'.,.,', 'egrammar.ra', 299) end .,., -module_eval(<<'.,.,', 'egrammar.ra', 303) - def _reduce_97(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 284) + def _reduce_91(val, _values, result) result = nil # don't think a nop is needed here either result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 311) - def _reduce_98(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 292) + def _reduce_92(val, _values, result) result = Factory.CASE(val[1], *val[3]) loc result, val[0], val[4] @@ -1593,22 +1550,22 @@ module_eval(<<'.,.,', 'egrammar.ra', 311) end .,., -module_eval(<<'.,.,', 'egrammar.ra', 317) - def _reduce_99(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 298) + def _reduce_93(val, _values, result) result = [val[0]] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 318) - def _reduce_100(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 299) + def _reduce_94(val, _values, result) result = val[0].push val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 323) - def _reduce_101(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 304) + def _reduce_95(val, _values, result) result = Factory.WHEN(val[0], val[3]) loc result, val[1], val[4] @@ -1616,8 +1573,8 @@ module_eval(<<'.,.,', 'egrammar.ra', 323) end .,., -module_eval(<<'.,.,', 'egrammar.ra', 327) - def _reduce_102(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 308) + def _reduce_96(val, _values, result) result = Factory.WHEN(val[0], nil) loc result, val[1], val[3] @@ -1625,54 +1582,54 @@ module_eval(<<'.,.,', 'egrammar.ra', 327) end .,., -module_eval(<<'.,.,', 'egrammar.ra', 331) - def _reduce_103(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 312) + def _reduce_97(val, _values, result) result = val[0] result end .,., -# reduce 104 omitted +# reduce 98 omitted -module_eval(<<'.,.,', 'egrammar.ra', 342) - def _reduce_105(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 323) + def _reduce_99(val, _values, result) result = val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 347) - def _reduce_106(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 328) + def _reduce_100(val, _values, result) result = [val[0]] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 348) - def _reduce_107(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 329) + def _reduce_101(val, _values, result) result = val[0].push val[2] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 353) - def _reduce_108(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 334) + def _reduce_102(val, _values, result) result = Factory.MAP(val[0], val[2]) ; loc result, val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 369) - def _reduce_109(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 350) + def _reduce_103(val, _values, result) result = val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 372) - def _reduce_110(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 353) + def _reduce_104(val, _values, result) result = case Factory.resource_shape(val[1]) when :resource, :class tmp = Factory.RESOURCE(Factory.fqn(token_text(val[1])), val[3]) @@ -1691,8 +1648,8 @@ module_eval(<<'.,.,', 'egrammar.ra', 372) end .,., -module_eval(<<'.,.,', 'egrammar.ra', 387) - def _reduce_111(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 368) + def _reduce_105(val, _values, result) result = case Factory.resource_shape(val[1]) when :resource, :class error "Defaults are not virtualizable" @@ -1708,8 +1665,8 @@ module_eval(<<'.,.,', 'egrammar.ra', 387) end .,., -module_eval(<<'.,.,', 'egrammar.ra', 399) - def _reduce_112(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 380) + def _reduce_106(val, _values, result) result = case Factory.resource_shape(val[0]) when :resource, :class Factory.RESOURCE(Factory.fqn(token_text(val[0])), val[2]) @@ -1726,8 +1683,8 @@ module_eval(<<'.,.,', 'egrammar.ra', 399) end .,., -module_eval(<<'.,.,', 'egrammar.ra', 412) - def _reduce_113(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 393) + def _reduce_107(val, _values, result) result = case Factory.resource_shape(val[0]) when :resource, :class # This catches deprecated syntax. @@ -1746,8 +1703,8 @@ module_eval(<<'.,.,', 'egrammar.ra', 412) end .,., -module_eval(<<'.,.,', 'egrammar.ra', 427) - def _reduce_114(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 408) + def _reduce_108(val, _values, result) result = Factory.RESOURCE(Factory.fqn(token_text(val[0])), val[2]) loc result, val[0], val[4] @@ -1755,50 +1712,50 @@ module_eval(<<'.,.,', 'egrammar.ra', 427) end .,., -module_eval(<<'.,.,', 'egrammar.ra', 432) - def _reduce_115(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 413) + def _reduce_109(val, _values, result) result = Factory.RESOURCE_BODY(val[0], val[2]) result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 434) - def _reduce_116(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 415) + def _reduce_110(val, _values, result) result = val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 437) - def _reduce_117(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 418) + def _reduce_111(val, _values, result) result = [val[0]] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 438) - def _reduce_118(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 419) + def _reduce_112(val, _values, result) result = val[0].push val[2] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 443) - def _reduce_119(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 424) + def _reduce_113(val, _values, result) result = :virtual result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 444) - def _reduce_120(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 425) + def _reduce_114(val, _values, result) result = :exported result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 456) - def _reduce_121(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 437) + def _reduce_115(val, _values, result) result = Factory.COLLECT(val[0], val[1], val[3]) loc result, val[0], val[5] @@ -1806,8 +1763,8 @@ module_eval(<<'.,.,', 'egrammar.ra', 456) end .,., -module_eval(<<'.,.,', 'egrammar.ra', 460) - def _reduce_122(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 441) + def _reduce_116(val, _values, result) result = Factory.COLLECT(val[0], val[1], []) loc result, val[0], val[1] @@ -1815,53 +1772,53 @@ module_eval(<<'.,.,', 'egrammar.ra', 460) end .,., -module_eval(<<'.,.,', 'egrammar.ra', 465) - def _reduce_123(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 446) + def _reduce_117(val, _values, result) result = Factory.VIRTUAL_QUERY(val[1]) ; loc result, val[0], val[2] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 466) - def _reduce_124(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 447) + def _reduce_118(val, _values, result) result = Factory.EXPORTED_QUERY(val[1]) ; loc result, val[0], val[2] result end .,., -# reduce 125 omitted +# reduce 119 omitted -# reduce 126 omitted +# reduce 120 omitted -module_eval(<<'.,.,', 'egrammar.ra', 479) - def _reduce_127(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 460) + def _reduce_121(val, _values, result) result = [] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 480) - def _reduce_128(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 461) + def _reduce_122(val, _values, result) result = [val[0]] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 481) - def _reduce_129(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 462) + def _reduce_123(val, _values, result) result = val[0].push(val[2]) result end .,., -# reduce 130 omitted +# reduce 124 omitted -# reduce 131 omitted +# reduce 125 omitted -# reduce 132 omitted +# reduce 126 omitted -module_eval(<<'.,.,', 'egrammar.ra', 497) - def _reduce_133(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 478) + def _reduce_127(val, _values, result) result = Factory.ATTRIBUTE_OP(val[0][:value], :'=>', val[2]) loc result, val[0], val[2] @@ -1869,8 +1826,8 @@ module_eval(<<'.,.,', 'egrammar.ra', 497) end .,., -module_eval(<<'.,.,', 'egrammar.ra', 501) - def _reduce_134(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 482) + def _reduce_128(val, _values, result) result = Factory.ATTRIBUTE_OP(val[0][:value], :'+>', val[2]) loc result, val[0], val[2] @@ -1878,8 +1835,8 @@ module_eval(<<'.,.,', 'egrammar.ra', 501) end .,., -module_eval(<<'.,.,', 'egrammar.ra', 511) - def _reduce_135(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 492) + def _reduce_129(val, _values, result) result = Factory.DEFINITION(classname(val[1][:value]), val[2], val[4]) loc result, val[0], val[5] @lexer.indefine = false @@ -1888,8 +1845,8 @@ module_eval(<<'.,.,', 'egrammar.ra', 511) end .,., -module_eval(<<'.,.,', 'egrammar.ra', 516) - def _reduce_136(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 497) + def _reduce_130(val, _values, result) result = Factory.DEFINITION(classname(val[1][:value]), val[2], nil) loc result, val[0], val[4] @lexer.indefine = false @@ -1898,8 +1855,8 @@ module_eval(<<'.,.,', 'egrammar.ra', 516) end .,., -module_eval(<<'.,.,', 'egrammar.ra', 531) - def _reduce_137(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 512) + def _reduce_131(val, _values, result) @lexer.namepop result = Factory.HOSTCLASS(classname(val[1][:value]), val[2], token_text(val[3]), val[5]) loc result, val[0], val[6] @@ -1908,8 +1865,8 @@ module_eval(<<'.,.,', 'egrammar.ra', 531) end .,., -module_eval(<<'.,.,', 'egrammar.ra', 536) - def _reduce_138(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 517) + def _reduce_132(val, _values, result) @lexer.namepop result = Factory.HOSTCLASS(classname(val[1][:value]), val[2], token_text(val[3]), nil) loc result, val[0], val[5] @@ -1918,21 +1875,21 @@ module_eval(<<'.,.,', 'egrammar.ra', 536) end .,., -# reduce 139 omitted +# reduce 133 omitted -module_eval(<<'.,.,', 'egrammar.ra', 544) - def _reduce_140(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 525) + def _reduce_134(val, _values, result) result = val[1] result end .,., -# reduce 141 omitted +# reduce 135 omitted -# reduce 142 omitted +# reduce 136 omitted -module_eval(<<'.,.,', 'egrammar.ra', 561) - def _reduce_143(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 542) + def _reduce_137(val, _values, result) result = Factory.NODE(val[1], val[2], val[4]) loc result, val[0], val[5] @@ -1940,8 +1897,8 @@ module_eval(<<'.,.,', 'egrammar.ra', 561) end .,., -module_eval(<<'.,.,', 'egrammar.ra', 565) - def _reduce_144(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 546) + def _reduce_138(val, _values, result) result = Factory.NODE(val[1], val[2], nil) loc result, val[0], val[4] @@ -1949,307 +1906,319 @@ module_eval(<<'.,.,', 'egrammar.ra', 565) end .,., -module_eval(<<'.,.,', 'egrammar.ra', 575) - def _reduce_145(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 556) + def _reduce_139(val, _values, result) result = [result] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 576) - def _reduce_146(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 557) + def _reduce_140(val, _values, result) result = val[0].push(val[2]) result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 581) - def _reduce_147(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 562) + def _reduce_141(val, _values, result) result = Factory.fqn(val[0][:value]); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 582) - def _reduce_148(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 563) + def _reduce_142(val, _values, result) result = val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 583) - def _reduce_149(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 564) + def _reduce_143(val, _values, result) result = Factory.literal(:default); loc result, val[0] result end .,., -# reduce 150 omitted +# reduce 144 omitted -# reduce 151 omitted +# reduce 145 omitted -module_eval(<<'.,.,', 'egrammar.ra', 589) - def _reduce_152(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 570) + def _reduce_146(val, _values, result) result = val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 594) - def _reduce_153(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 575) + def _reduce_147(val, _values, result) result = val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 595) - def _reduce_154(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 576) + def _reduce_148(val, _values, result) result = val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 599) - def _reduce_155(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 580) + def _reduce_149(val, _values, result) result = [] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 600) - def _reduce_156(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 581) + def _reduce_150(val, _values, result) result = [] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 601) - def _reduce_157(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 582) + def _reduce_151(val, _values, result) result = val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 605) - def _reduce_158(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 586) + def _reduce_152(val, _values, result) result = [val[0]] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 606) - def _reduce_159(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 587) + def _reduce_153(val, _values, result) result = val[0].push(val[2]) result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 610) - def _reduce_160(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 591) + def _reduce_154(val, _values, result) result = Factory.PARAM(val[0][:value], val[2]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 611) - def _reduce_161(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 592) + def _reduce_155(val, _values, result) result = Factory.PARAM(val[0][:value]); loc result, val[0] result end .,., -# reduce 162 omitted +# reduce 156 omitted -# reduce 163 omitted +# reduce 157 omitted -module_eval(<<'.,.,', 'egrammar.ra', 624) - def _reduce_164(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 605) + def _reduce_158(val, _values, result) result = Factory.fqn(val[0][:value]).var ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 630) - def _reduce_165(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 611) + def _reduce_159(val, _values, result) result = Factory.LIST(val[1]); loc result, val[0], val[2] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 631) - def _reduce_166(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 612) + def _reduce_160(val, _values, result) result = Factory.LIST(val[1]); loc result, val[0], val[3] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 632) - def _reduce_167(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 613) + def _reduce_161(val, _values, result) result = Factory.literal([]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 635) - def _reduce_168(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 616) + def _reduce_162(val, _values, result) result = Factory.HASH(val[1]); loc result, val[0], val[2] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 636) - def _reduce_169(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 617) + def _reduce_163(val, _values, result) result = Factory.HASH(val[1]); loc result, val[0], val[3] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 637) - def _reduce_170(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 618) + def _reduce_164(val, _values, result) result = Factory.literal({}) ; loc result, val[0], val[3] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 640) - def _reduce_171(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 621) + def _reduce_165(val, _values, result) result = [val[0]] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 641) - def _reduce_172(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 622) + def _reduce_166(val, _values, result) result = val[0].push val[2] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 644) - def _reduce_173(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 625) + def _reduce_167(val, _values, result) result = Factory.KEY_ENTRY(val[0], val[2]); loc result, val[1] result end .,., -# reduce 174 omitted +# reduce 168 omitted -# reduce 175 omitted +# reduce 169 omitted -module_eval(<<'.,.,', 'egrammar.ra', 650) - def _reduce_176(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 631) + def _reduce_170(val, _values, result) result = Factory.literal(val[0][:value]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 651) - def _reduce_177(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 632) + def _reduce_171(val, _values, result) result = Factory.string(val[0], *val[1]) ; loc result, val[0], val[1][-1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 652) - def _reduce_178(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 633) + def _reduce_172(val, _values, result) result = Factory.literal(val[0][:value]); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 653) - def _reduce_179(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 634) + def _reduce_173(val, _values, result) result = Factory.literal(val[0][:value]); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 654) - def _reduce_180(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 635) + def _reduce_174(val, _values, result) result = Factory.literal(val[0][:value]); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 655) - def _reduce_181(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 636) + def _reduce_175(val, _values, result) result = [val[0]] + val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 656) - def _reduce_182(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 637) + def _reduce_176(val, _values, result) result = Factory.TEXT(val[0]) result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 659) - def _reduce_183(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 640) + def _reduce_177(val, _values, result) result = [val[0]] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 660) - def _reduce_184(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 641) + def _reduce_178(val, _values, result) result = [val[0]] + val[1] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 662) - def _reduce_185(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 643) + def _reduce_179(val, _values, result) result = Factory.QNAME_OR_NUMBER(val[0][:value]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 663) - def _reduce_186(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 644) + def _reduce_180(val, _values, result) result = Factory.QREF(val[0][:value]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 664) - def _reduce_187(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 645) + def _reduce_181(val, _values, result) result = Factory.literal(:undef); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 665) - def _reduce_188(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 646) + def _reduce_182(val, _values, result) result = Factory.literal(:default); loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 670) - def _reduce_189(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 651) + def _reduce_183(val, _values, result) result = Factory.literal(val[0][:value]) ; loc result, val[0] result end .,., -module_eval(<<'.,.,', 'egrammar.ra', 673) - def _reduce_190(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 654) + def _reduce_184(val, _values, result) result = Factory.literal(val[0][:value]); loc result, val[0] result end .,., -# reduce 191 omitted +# reduce 185 omitted -module_eval(<<'.,.,', 'egrammar.ra', 679) - def _reduce_192(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 660) + def _reduce_186(val, _values, result) result = nil result end .,., +# reduce 187 omitted + +# reduce 188 omitted + +# reduce 189 omitted + +# reduce 190 omitted + +# reduce 191 omitted + +# reduce 192 omitted + # reduce 193 omitted # reduce 194 omitted @@ -2270,20 +2239,8 @@ module_eval(<<'.,.,', 'egrammar.ra', 679) # reduce 202 omitted -# reduce 203 omitted - -# reduce 204 omitted - -# reduce 205 omitted - -# reduce 206 omitted - -# reduce 207 omitted - -# reduce 208 omitted - -module_eval(<<'.,.,', 'egrammar.ra', 702) - def _reduce_209(val, _values, result) +module_eval(<<'.,.,', 'egrammar.ra', 683) + def _reduce_203(val, _values, result) result = nil result end diff --git a/lib/puppet/pops/parser/lexer.rb b/lib/puppet/pops/parser/lexer.rb index 1e090f7bc..994c4643f 100644 --- a/lib/puppet/pops/parser/lexer.rb +++ b/lib/puppet/pops/parser/lexer.rb @@ -148,8 +148,8 @@ class Puppet::Pops::Parser::Lexer TOKENS.add_tokens( '[' => :LBRACK, ']' => :RBRACK, - # '{' => :LBRACE, # Specialized to handle lambda - '}' => :RBRACE, + # '{' => :LBRACE, # Specialized to handle lambda and brace count + # '}' => :RBRACE, # Specialized to handle brace count '(' => :LPAREN, ')' => :RPAREN, '=' => :EQUALS, @@ -194,7 +194,6 @@ class Puppet::Pops::Parser::Lexer "<dqstring between two interpolations>" => :DQMID, "<dqstring after final interpolation>" => :DQPOST, "<boolean>" => :BOOLEAN, - "<lambda start>" => :LAMBDA, # A LBRACE followed by '|' "<select start>" => :SELBRACE # A QMARK followed by '{' ) @@ -214,10 +213,6 @@ class Puppet::Pops::Parser::Lexer REGEX_INTRODUCING_TOKENS.include? context[:after] end - IN_STRING_INTERPOLATION = Proc.new do |context| - context[:string_interpolation_depth] > 0 - end - DASHED_VARIABLES_ALLOWED = Proc.new do |context| Puppet[:allow_variables_with_dashes] end @@ -227,20 +222,6 @@ class Puppet::Pops::Parser::Lexer end end - # LBRACE needs look ahead to differentiate between '{' and a '{' - # followed by a '|' (start of lambda) The racc grammar can only do one - # token lookahead. - # - TOKENS.add_token :LBRACE, /\{/ do | lexer, value | - if lexer.match?(/[ \t\r]*\|/) - [TOKENS[:LAMBDA], value] - elsif lexer.lexing_context[:after] == :QMARK - [TOKENS[:SELBRACE], value] - else - [TOKENS[:LBRACE], value] - end - end - # Numbers are treated separately from names, so that they may contain dots. TOKENS.add_token :NUMBER, %r{\b(?:0[xX][0-9A-Fa-f]+|0?\d+(?:\.\d+)?(?:[eE]-?\d+)?)\b} do |lexer, value| lexer.assert_numeric(value) @@ -305,10 +286,31 @@ class Puppet::Pops::Parser::Lexer lexer.tokenize_interpolated_string(DQ_initial_token_types) end - TOKENS.add_token :DQCONT, /\}/ do |lexer, value| - lexer.tokenize_interpolated_string(DQ_continuation_token_types) + + # LBRACE needs look ahead to differentiate between '{' and a '{' + # followed by a '|' (start of lambda) The racc grammar can only do one + # token lookahead. + # + TOKENS.add_token :LBRACE, "{" do |lexer, value| + lexer.lexing_context[:brace_count] += 1 + if lexer.lexing_context[:after] == :QMARK + [TOKENS[:SELBRACE], value] + else + [TOKENS[:LBRACE], value] + end + end + + # RBRACE needs to differentiate between a regular brace that is part of + # syntax and one that is the ending of a string interpolation. + TOKENS.add_token :RBRACE, "}" do |lexer, value| + context = lexer.lexing_context + if context[:interpolation_stack].empty? || context[:brace_count] != context[:interpolation_stack][-1] + context[:brace_count] -= 1 + [TOKENS[:RBRACE], value] + else + lexer.tokenize_interpolated_string(DQ_continuation_token_types) + end end - TOKENS[:DQCONT].acceptable_when Contextual::IN_STRING_INTERPOLATION TOKENS.add_token :DOLLAR_VAR_WITH_DASH, %r{\$(?:::)?(?:[-\w]+::)*[-\w]+} do |lexer, value| lexer.warn_if_variable_has_hyphen(value) @@ -339,9 +341,21 @@ class Puppet::Pops::Parser::Lexer # reference. # if lexer.match?(%r{[ \t\r]*\(}) - [TOKENS[:NAME],value] + # followed by ( is a function call + [TOKENS[:NAME], value] + + elsif kwd_token = KEYWORDS.lookup(value) + # true, false, if, unless, case, and undef are keywords that cannot be used as variables + # but node, and several others are variables + if [ :TRUE, :FALSE ].include?(kwd_token.name) + [ TOKENS[:BOOLEAN], eval(value) ] + elsif [ :IF, :UNLESS, :CASE, :UNDEF ].include?(kwd_token.name) + [kwd_token, value] + else + [TOKENS[:VARIABLE], value] + end else - [TOKENS[:VARIABLE],value] + [TOKENS[:VARIABLE], value] end end @@ -404,7 +418,7 @@ class Puppet::Pops::Parser::Lexer def file=(file) @file = file - contents = File.exists?(file) ? File.read(file) : "" + contents = Puppet::FileSystem::File.exist?(file) ? File.read(file) : "" @scanner = StringScanner.new(contents) @locator = Locator.new(contents, multibyte?) end @@ -501,7 +515,8 @@ class Puppet::Pops::Parser::Lexer :start_of_line => true, :offset => 0, # byte offset before where token starts :end_offset => 0, # byte offset after scanned token - :string_interpolation_depth => 0 + :brace_count => 0, # nested depth of braces + :interpolation_stack => [] # matching interpolation brace level } end @@ -592,8 +607,11 @@ class Puppet::Pops::Parser::Lexer end lexing_context[:after] = final_token.name unless newline - lexing_context[:string_interpolation_depth] += 1 if final_token.name == :DQPRE - lexing_context[:string_interpolation_depth] -= 1 if final_token.name == :DQPOST + if final_token.name == :DQPRE + lexing_context[:interpolation_stack] << lexing_context[:brace_count] + elsif final_token.name == :DQPOST + lexing_context[:interpolation_stack].pop + end value = token_value[:value] diff --git a/lib/puppet/pops/parser/parser_support.rb b/lib/puppet/pops/parser/parser_support.rb index db35aad01..a22de7161 100644 --- a/lib/puppet/pops/parser/parser_support.rb +++ b/lib/puppet/pops/parser/parser_support.rb @@ -63,7 +63,7 @@ class Puppet::Pops::Parser::Parser # Parses a file expected to contain pp DSL logic. def parse_file(file) - unless FileTest.exist?(file) + unless Puppet::FileSystem::File.exist?(file) unless file =~ /\.pp$/ file = file + ".pp" end diff --git a/lib/puppet/pops/patterns.rb b/lib/puppet/pops/patterns.rb index d18384fcb..b1c76ad43 100644 --- a/lib/puppet/pops/patterns.rb +++ b/lib/puppet/pops/patterns.rb @@ -18,18 +18,18 @@ module Puppet::Pops::Patterns # NAME matches a name the same way as the lexer. # This name includes hyphen, which may be illegal in variables, and names in general. - NAME = %r{((::)?[a-z0-9][-\w]*)(::[a-z0-9][-\w]*)*} + NAME = %r{\A((::)?[a-z0-9]\w*)(::[a-z0-9]\w*)*\z} # CLASSREF_EXT matches a class reference the same way as the lexer - i.e. the external source form # where each part must start with a capital letter A-Z. # This name includes hyphen, which may be illegal in some cases. # - CLASSREF_EXT = %r{((::){0,1}[A-Z][-\w]*)+} + CLASSREF_EXT = %r{\A((::){0,1}[A-Z][-\w]*)+\z} - # CLASSREF matches a class reference the way it is represented internall in the + # CLASSREF matches a class reference the way it is represented internally in the # model (i.e. in lower case). # This name includes hyphen, which may be illegal in some cases. # - CLASSREF = %r{((::){0,1}[a-z][-\w]*)+} + CLASSREF = %r{\A((::){0,1}[a-z][-\w]*)+\z} end diff --git a/lib/puppet/pops/utils.rb b/lib/puppet/pops/utils.rb index 104a26951..01a540432 100644 --- a/lib/puppet/pops/utils.rb +++ b/lib/puppet/pops/utils.rb @@ -35,7 +35,7 @@ module Puppet::Pops::Utils radix = 10 if match[1].to_s.length > 0 radix = 16 - elsif match[2].to_s.length > 0 && match[2][0] == '0' + elsif match[2].to_s.length > 1 && match[2][0] == '0' radix = 8 end [Integer(match[0], radix), radix] diff --git a/lib/puppet/pops/validation/checker3_1.rb b/lib/puppet/pops/validation/checker3_1.rb index 65cbbbeaf..839df5044 100644 --- a/lib/puppet/pops/validation/checker3_1.rb +++ b/lib/puppet/pops/validation/checker3_1.rb @@ -20,7 +20,7 @@ class Puppet::Pops::Validation::Checker3_1 def initialize(diagnostics_producer) @@check_visitor ||= Puppet::Pops::Visitor.new(nil, "check", 0, 0) @@rvalue_visitor ||= Puppet::Pops::Visitor.new(nil, "rvalue", 0, 0) - @@hostname_visitor ||= Puppet::Pops::Visitor.new(nil, "hostname", 1, 1) + @@hostname_visitor ||= Puppet::Pops::Visitor.new(nil, "hostname", 1, 2) @@assignment_visitor ||= Puppet::Pops::Visitor.new(nil, "assign", 0, 1) @@query_visitor ||= Puppet::Pops::Visitor.new(nil, "query", 0, 0) @@top_visitor ||= Puppet::Pops::Visitor.new(nil, "top", 1, 1) @@ -45,8 +45,9 @@ class Puppet::Pops::Validation::Checker3_1 end # Performs check if this is a vaid hostname expression - def hostname(o, semantic) - @@hostname_visitor.visit_this(self, o, semantic) + # @param single_feature_name [String, nil] the name of a single valued hostname feature of the value's container. e.g. 'parent' + def hostname(o, semantic, single_feature_name = nil) + @@hostname_visitor.visit_this(self, o, semantic, single_feature_name) end # Performs check if this is valid as a query @@ -125,7 +126,7 @@ class Puppet::Pops::Validation::Checker3_1 case o.left_expr when Model::QualifiedName # allows many keys, but the name should really be a QualifiedReference - acceptor.accept(Issues::DEPRECATED_NAME_AS_TYPE, o, :name => o.value) + acceptor.accept(Issues::DEPRECATED_NAME_AS_TYPE, o, :name => o.left_expr.value) when Model::QualifiedReference # ok, allows many - this is a resource reference @@ -255,6 +256,7 @@ class Puppet::Pops::Validation::Checker3_1 def check_NodeDefinition(o) # Check that hostnames are valid hostnames (or regular expressons) hostname(o.host_matches, o) + hostname(o.parent, o, 'parent') unless o.parent.nil? top(o.eContainer, o) end @@ -385,11 +387,14 @@ class Puppet::Pops::Validation::Checker3_1 #--- HOSTNAME CHECKS # Transforms Array of host matching expressions into a (Ruby) array of AST::HostName - def hostname_Array(o, semantic) - o.each {|x| hostname x, semantic } + def hostname_Array(o, semantic, single_feature_name) + if single_feature_name + acceptor.accept(Issues::ILLEGAL_EXPRESSION, o, {:feature=>single_feature_name, :container=>semantic}) + end + o.each {|x| hostname(x, semantic, false) } end - def hostname_String(o, semantic) + def hostname_String(o, semantic, single_feature_name) # The 3.x checker only checks for illegal characters - if matching /[^-\w.]/ the name is invalid, # but this allows pathological names like "a..b......c", "----" # TODO: Investigate if more illegal hostnames should be flagged. @@ -399,11 +404,11 @@ class Puppet::Pops::Validation::Checker3_1 end end - def hostname_LiteralValue(o, semantic) - hostname_String(o.value.to_s, o) + def hostname_LiteralValue(o, semantic, single_feature_name) + hostname_String(o.value.to_s, o, single_feature_name) end - def hostname_ConcatenatedString(o, semantic) + def hostname_ConcatenatedString(o, semantic, single_feature_name) # Puppet 3.1. only accepts a concatenated string without interpolated expressions if the_expr = o.segments.index {|s| s.is_a?(Model::TextExpression) } acceptor.accept(Issues::ILLEGAL_HOSTNAME_INTERPOLATION, o.segments[the_expr].expr) @@ -413,32 +418,32 @@ class Puppet::Pops::Validation::Checker3_1 else # corner case, may be ok, but lexer may have replaced with plain string, this is # here if it does not - hostname_String(o.segments[0], o.segments[0]) + hostname_String(o.segments[0], o.segments[0], false) end end - def hostname_QualifiedName(o, semantic) - hostname_String(o.value.to_s, o) + def hostname_QualifiedName(o, semantic, single_feature_name) + hostname_String(o.value.to_s, o, single_feature_name) end - def hostname_QualifiedReference(o, semantic) - hostname_String(o.value.to_s, o) + def hostname_QualifiedReference(o, semantic, single_feature_name) + hostname_String(o.value.to_s, o, single_feature_name) end - def hostname_LiteralNumber(o, semantic) + def hostname_LiteralNumber(o, semantic, single_feature_name) # always ok end - def hostname_LiteralDefault(o, semantic) + def hostname_LiteralDefault(o, semantic, single_feature_name) # always ok end - def hostname_LiteralRegularExpression(o, semantic) + def hostname_LiteralRegularExpression(o, semantic, single_feature_name) # always ok end - def hostname_Object(o, semantic) - acceptor.accept(Issues::ILLEGAL_EXPRESSION, o, {:feature=>'hostname', :container=>semantic}) + def hostname_Object(o, semantic, single_feature_name) + acceptor.accept(Issues::ILLEGAL_EXPRESSION, o, {:feature=> single_feature_name || 'hostname', :container=>semantic}) end #---QUERY CHECKS diff --git a/lib/puppet/provider.rb b/lib/puppet/provider.rb index f875e3df0..d88eebea3 100644 --- a/lib/puppet/provider.rb +++ b/lib/puppet/provider.rb @@ -40,10 +40,10 @@ class Puppet::Provider include Puppet::Util::Warnings extend Puppet::Util::Warnings - require 'puppet/provider/confiner' + require 'puppet/confiner' require 'puppet/provider/command' - extend Puppet::Provider::Confiner + extend Puppet::Confiner Puppet::Util.logmethods(self, true) @@ -148,6 +148,7 @@ class Puppet::Provider # @raise [Puppet::DevError] if the name does not reference an existing command. # @return [String] the absolute path to the found executable for the command # @see which + # @api public def self.command(name) name = name.intern @@ -163,7 +164,7 @@ class Puppet::Provider end # Confines this provider to be suitable only on hosts where the given commands are present. - # Also see {Puppet::Provider::Confiner#confine} for other types of confinement of a provider by use of other types of + # Also see {Puppet::Confiner#confine} for other types of confinement of a provider by use of other types of # predicates. # # @note It is preferred if the commands are not entered with absolute paths as this allows puppet @@ -173,6 +174,7 @@ class Puppet::Provider # be executing on the system. Each command is specified with a name and the path of the executable. # @return [void] # @see optional_commands + # @api public # def self.commands(command_specs) command_specs.each do |name, path| @@ -189,6 +191,7 @@ class Puppet::Provider # be executing on the system. Each command is specified with a name and the path of the executable. # (@see #has_command) # @see commands + # @api public def self.optional_commands(hash) hash.each do |name, target| has_command(name, target) do @@ -221,6 +224,7 @@ class Puppet::Provider # @comment a yield [ ] produces {|| ...} in the signature, do not remove the space. # @note the name ´has_command´ looks odd in an API context, but makes more sense when seen in the internal # DSL context where a Provider is declaratively defined. + # @api public # def self.has_command(name, path, &block) name = name.intern @@ -485,7 +489,7 @@ class Puppet::Provider if @defaults.length > 0 return "Default for " + @defaults.collect do |f, v| "`#{f}` == `#{[v].flatten.join(', ')}`" - end.join(" and ") + "." + end.sort.join(" and ") + "." end end @@ -493,7 +497,7 @@ class Puppet::Provider if @commands.length > 0 return "Required binaries: " + @commands.collect do |n, c| "`#{c}`" - end.join(", ") + "." + end.sort.join(", ") + "." end end @@ -501,7 +505,7 @@ class Puppet::Provider if features.length > 0 return "Supported features: " + features.collect do |f| "`#{f}`" - end.join(", ") + "." + end.sort.join(", ") + "." end end @@ -602,6 +606,18 @@ class Puppet::Provider # fetched state (i.e. what is returned from the {instances} method). # @param resources_hash [Hash<{String => Puppet::Resource}>] map from name to resource of resources to prefetch # @return [void] + # @api public + + # @comment Document post_resource_eval here as it does not exist anywhere else (called from transaction if implemented) + # @!method self.post_resource_eval() + # @since 3.4.0 + # @api public + # @abstract A subclass may implement this - it is not implemented in the Provider class + # This method may be implemented by a provider in order to perform any + # cleanup actions needed. It will be called at the end of the transaction if + # any resources in the catalog make use of the provider, regardless of + # whether the resources are changed or not and even if resource failures occur. + # @return [void] # @comment Document flush here as it does not exist anywhere (called from transaction if implemented) # @!method flush() @@ -609,5 +625,6 @@ class Puppet::Provider # This method may be implemented by a provider in order to flush properties that has not been individually # applied to the managed entity's current state. # @return [void] + # @api public end diff --git a/lib/puppet/provider/aixobject.rb b/lib/puppet/provider/aixobject.rb index ed27b4e52..ed27b4e52 100755..100644 --- a/lib/puppet/provider/aixobject.rb +++ b/lib/puppet/provider/aixobject.rb diff --git a/lib/puppet/provider/augeas/augeas.rb b/lib/puppet/provider/augeas/augeas.rb index 2246636da..f0369a420 100644 --- a/lib/puppet/provider/augeas/augeas.rb +++ b/lib/puppet/provider/augeas/augeas.rb @@ -125,7 +125,7 @@ Puppet::Type.type(:augeas).provide(:augeas) do end fail("missing string argument #{narg} for #{cmd}") unless argline[-1] elsif f == :comparator - argline << sc.scan(/(==|!=|=~|<|<=|>|>=)/) + argline << sc.scan(/(==|!=|=~|<=|>=|<|>)/) unless argline[-1] puts sc.rest fail("invalid comparator for command #{cmd}") @@ -198,6 +198,17 @@ Puppet::Type.type(:augeas).provide(:augeas) do end end + def is_numeric?(s) + case s + when Fixnum + true + when String + s.match(/\A[+-]?\d+?(\.\d+)?\Z/n) == nil ? false : true + else + false + end + end + # Used by the need_to_run? method to process get filters. Returns # true if there is a match, false if otherwise # Assumes a syntax of get /files/path [COMPARATOR] value @@ -213,10 +224,15 @@ Puppet::Type.type(:augeas).provide(:augeas) do #check the value in augeas result = @aug.get(path) || '' - case comparator - when "!=" + + if ['<', '<=', '>=', '>'].include? comparator and is_numeric?(result) and + is_numeric?(arg) + resultf = result.to_f + argf = arg.to_f + return_value = (resultf.send(comparator, argf)) + elsif comparator == "!=" return_value = (result != arg) - when "=~" + elsif comparator == "=~" regex = Regexp.new(arg) return_value = (result =~ regex) else @@ -292,7 +308,7 @@ Puppet::Type.type(:augeas).provide(:augeas) do load_path.flatten! end - if File.exists?("#{Puppet[:libdir]}/augeas/lenses") + if Puppet::FileSystem::File.exist?("#{Puppet[:libdir]}/augeas/lenses") load_path << "#{Puppet[:libdir]}/augeas/lenses" end diff --git a/lib/puppet/provider/confine.rb b/lib/puppet/provider/confine.rb index b28d07df3..4804465b0 100644 --- a/lib/puppet/provider/confine.rb +++ b/lib/puppet/provider/confine.rb @@ -1,80 +1,6 @@ -# The class that handles testing whether our providers -# actually work or not. -require 'puppet/util' +# Confines have been moved out of the provider as they are also used for other things. +# This provides backwards compatibility for people still including this old location. +require 'puppet/provider' +require 'puppet/confine' -class Puppet::Provider::Confine - include Puppet::Util - - @tests = {} - - class << self - attr_accessor :name - end - - def self.inherited(klass) - name = klass.to_s.split("::").pop.downcase.to_sym - raise "Test #{name} is already defined" if @tests.include?(name) - - klass.name = name - - @tests[name] = klass - end - - def self.test(name) - unless @tests[name] - begin - require "puppet/provider/confine/#{name}" - rescue LoadError => detail - unless detail.to_s =~ /No such file|cannot load such file/i - warn "Could not load confine test '#{name}': #{detail}" - end - # Could not find file - end - end - @tests[name] - end - - attr_reader :values - - # Mark that this confine is used for testing binary existence. - attr_accessor :for_binary - def for_binary? - for_binary - end - - # Used for logging. - attr_accessor :label - - def initialize(values) - values = [values] unless values.is_a?(Array) - @values = values - end - - # Provide a hook for the message when there's a failure. - def message(value) - "" - end - - # Collect the results of all of them. - def result - values.collect { |value| pass?(value) } - end - - # Test whether our confine matches. - def valid? - values.each do |value| - unless pass?(value) - Puppet.debug(label + ": " + message(value)) - return false - end - end - - return true - ensure - reset - end - - # Provide a hook for subclasses. - def reset - end -end +Puppet::Provider::Confine = Puppet::Confine diff --git a/lib/puppet/provider/cron/crontab.rb b/lib/puppet/provider/cron/crontab.rb index 91047af78..91047af78 100755..100644 --- a/lib/puppet/provider/cron/crontab.rb +++ b/lib/puppet/provider/cron/crontab.rb diff --git a/lib/puppet/provider/exec.rb b/lib/puppet/provider/exec.rb index d5cd552fb..b0db9ff94 100644 --- a/lib/puppet/provider/exec.rb +++ b/lib/puppet/provider/exec.rb @@ -47,18 +47,17 @@ class Puppet::Provider::Exec < Puppet::Provider end end - Timeout::timeout(resource[:timeout]) do # note that we are passing "false" for the "override_locale" parameter, which ensures that the user's # default/system locale will be respected. Callers may override this behavior by setting locale-related # environment variables (LANG, LC_ALL, etc.) in their 'environment' configuration. - output, status = Puppet::Util::SUIDManager. - run_and_capture(command, resource[:user], resource[:group], - :override_locale => false, - :custom_environment => environment) + output = Puppet::Util::Execution.execute(command, :failonfail => false, :combine => true, + :uid => resource[:user], :gid => resource[:group], + :override_locale => false, + :custom_environment => environment) end # The shell returns 127 if the command is missing. - if status.exitstatus == 127 + if output.exitstatus == 127 raise ArgumentError, output end @@ -67,7 +66,10 @@ class Puppet::Provider::Exec < Puppet::Provider self.fail detail.to_s end - return output, status + # Return output twice as processstatus was returned before, but only exitstatus was ever called. + # Output has the exitstatus on it so it is returned instead. This is here twice as changing this + # would result in a change to the underlying API. + return output, output end def extractexe(command) diff --git a/lib/puppet/provider/exec/posix.rb b/lib/puppet/provider/exec/posix.rb index 82d6068ea..c552f9a03 100644 --- a/lib/puppet/provider/exec/posix.rb +++ b/lib/puppet/provider/exec/posix.rb @@ -1,6 +1,7 @@ require 'puppet/provider/exec' Puppet::Type.type(:exec).provide :posix, :parent => Puppet::Provider::Exec do + has_feature :umask confine :feature => :posix defaultfor :feature => :posix @@ -16,7 +17,7 @@ Puppet::Type.type(:exec).provide :posix, :parent => Puppet::Provider::Exec do exe = extractexe(command) if File.expand_path(exe) == exe - if !File.exists?(exe) + if !Puppet::FileSystem::File.exist?(exe) raise ArgumentError, "Could not find command '#{exe}'" elsif !File.file?(exe) raise ArgumentError, "'#{exe}' is a #{File.ftype(exe)}, not a file" @@ -36,4 +37,12 @@ Puppet::Type.type(:exec).provide :posix, :parent => Puppet::Provider::Exec do # distinguish not found from not executable raise ArgumentError, "Could not find command '#{exe}'" end + + def run(command, check = false) + if resource[:umask] + Puppet::Util::withumask(resource[:umask]) { super(command, check) } + else + super(command, check) + end + end end diff --git a/lib/puppet/provider/exec/windows.rb b/lib/puppet/provider/exec/windows.rb index 05b9af9e9..1c727ef0b 100644 --- a/lib/puppet/provider/exec/windows.rb +++ b/lib/puppet/provider/exec/windows.rb @@ -36,7 +36,7 @@ Puppet::Type.type(:exec).provide :windows, :parent => Puppet::Provider::Exec do exe = extractexe(command) if absolute_path?(exe) - if !File.exists?(exe) + if !Puppet::FileSystem::File.exist?(exe) raise ArgumentError, "Could not find command '#{exe}'" elsif !File.file?(exe) raise ArgumentError, "'#{exe}' is a #{File.ftype(exe)}, not a file" diff --git a/lib/puppet/provider/file/posix.rb b/lib/puppet/provider/file/posix.rb index 5ab84b48b..629a380dd 100644 --- a/lib/puppet/provider/file/posix.rb +++ b/lib/puppet/provider/file/posix.rb @@ -2,6 +2,7 @@ Puppet::Type.type(:file).provide :posix do desc "Uses POSIX functionality to manage file ownership and permissions." confine :feature => :posix + has_features :manages_symlinks include Puppet::Util::POSIX include Puppet::Util::Warnings diff --git a/lib/puppet/provider/file/windows.rb b/lib/puppet/provider/file/windows.rb index b3475ebe1..350948c47 100644 --- a/lib/puppet/provider/file/windows.rb +++ b/lib/puppet/provider/file/windows.rb @@ -2,6 +2,7 @@ Puppet::Type.type(:file).provide :windows do desc "Uses Microsoft Windows functionality to manage file ownership and permissions." confine :operatingsystem => :windows + has_feature :manages_symlinks if Puppet.features.manages_symlinks? include Puppet::Util::Warnings @@ -35,33 +36,37 @@ Puppet::Type.type(:file).provide :windows do alias :name2uid :name2id def owner - return :absent unless resource.exist? + return :absent unless resource.stat get_owner(resource[:path]) end def owner=(should) begin - set_owner(should, resource[:path]) + path = resource[:links] == :manage ? file.path.to_s : file.readlink + + set_owner(should, path) rescue => detail raise Puppet::Error, "Failed to set owner to '#{should}': #{detail}" end end def group - return :absent unless resource.exist? + return :absent unless resource.stat get_group(resource[:path]) end def group=(should) begin - set_group(should, resource[:path]) + path = resource[:links] == :manage ? file.path.to_s : file.readlink + + set_group(should, path) rescue => detail raise Puppet::Error, "Failed to set group to '#{should}': #{detail}" end end def mode - if resource.exist? + if resource.stat mode = get_mode(resource[:path]) mode ? mode.to_s(8) : :absent else @@ -85,4 +90,10 @@ Puppet::Type.type(:file).provide :windows do resource.fail("Can only manage owner, group, and mode on filesystems that support Windows ACLs, such as NTFS") end end + + attr_reader :file + private + def file + @file ||= Puppet::FileSystem::File.new(resource[:path]) + end end diff --git a/lib/puppet/provider/group/aix.rb b/lib/puppet/provider/group/aix.rb index 666748378..666748378 100755..100644 --- a/lib/puppet/provider/group/aix.rb +++ b/lib/puppet/provider/group/aix.rb diff --git a/lib/puppet/provider/group/windows_adsi.rb b/lib/puppet/provider/group/windows_adsi.rb index 5811fc593..56c0c175b 100644 --- a/lib/puppet/provider/group/windows_adsi.rb +++ b/lib/puppet/provider/group/windows_adsi.rb @@ -1,13 +1,45 @@ require 'puppet/util/adsi' Puppet::Type.type(:group).provide :windows_adsi do - desc "Local group management for Windows. Nested groups are not supported." + desc "Local group management for Windows. Group members can be both users and groups. + Additionally, local groups can contain domain users." defaultfor :operatingsystem => :windows confine :operatingsystem => :windows has_features :manages_members + def members_insync?(current, should) + return false unless current + + # By comparing account SIDs we don't have to worry about case + # sensitivity, or canonicalization of account names. + + # Cannot use munge of the group property to canonicalize @should + # since the default array_matching comparison is not commutative + should_empty = should.nil? or should.empty? + + return false if current.empty? != should_empty + + # dupes automatically weeded out when hashes built + Puppet::Util::ADSI::Group.name_sid_hash(current) == Puppet::Util::ADSI::Group.name_sid_hash(should) + end + + def members_to_s(users) + return '' if users.nil? or !users.kind_of?(Array) + users = users.map do |user_name| + sid = Puppet::Util::Windows::Security.name_to_sid_object(user_name) + if sid.account =~ /\\/ + account, _ = Puppet::Util::ADSI::User.parse_name(sid.account) + else + account = sid.account + end + resource.debug("#{sid.domain}\\#{account} (#{sid.to_s})") + "#{sid.domain}\\#{account}" + end + return users.join(',') + end + def group @group ||= Puppet::Util::ADSI::Group.new(@resource[:name]) end diff --git a/lib/puppet/provider/macauthorization/macauthorization.rb b/lib/puppet/provider/macauthorization/macauthorization.rb index fe9c56985..e410d6f43 100644 --- a/lib/puppet/provider/macauthorization/macauthorization.rb +++ b/lib/puppet/provider/macauthorization/macauthorization.rb @@ -17,7 +17,7 @@ Puppet::Type.type(:macauthorization).provide :macauthorization, :parent => Puppe # This should be confined based on macosx_productversion # but puppet resource doesn't make the facts available and # that interface is heavily used with this provider. - if FileTest.exists?("/usr/bin/sw_vers") + if Puppet::FileSystem::File.exist?("/usr/bin/sw_vers") product_version = sw_vers "-productVersion" confine :true => unless /^10\.[0-4]/.match(product_version) diff --git a/lib/puppet/provider/mailalias/aliases.rb b/lib/puppet/provider/mailalias/aliases.rb index 87ee5b465..87ee5b465 100755..100644 --- a/lib/puppet/provider/mailalias/aliases.rb +++ b/lib/puppet/provider/mailalias/aliases.rb diff --git a/lib/puppet/provider/maillist/mailman.rb b/lib/puppet/provider/maillist/mailman.rb index e070a25dd..e070a25dd 100755..100644 --- a/lib/puppet/provider/maillist/mailman.rb +++ b/lib/puppet/provider/maillist/mailman.rb diff --git a/lib/puppet/provider/mount/parsed.rb b/lib/puppet/provider/mount/parsed.rb index d935d7d13..d935d7d13 100755..100644 --- a/lib/puppet/provider/mount/parsed.rb +++ b/lib/puppet/provider/mount/parsed.rb diff --git a/lib/puppet/provider/nameservice/directoryservice.rb b/lib/puppet/provider/nameservice/directoryservice.rb index f0cbecea4..e8fe9016f 100644 --- a/lib/puppet/provider/nameservice/directoryservice.rb +++ b/lib/puppet/provider/nameservice/directoryservice.rb @@ -251,7 +251,7 @@ class Puppet::Provider::NameService::DirectoryService < Puppet::Provider::NameSe Please check your password and try again.") end - if File.exists?("#{users_plist_dir}/#{resource_name}.plist") + if Puppet::FileSystem::File.exist?("#{users_plist_dir}/#{resource_name}.plist") # If a plist already exists in /var/db/dslocal/nodes/Default/users, then # we will need to extract the binary plist from the 'ShadowHashData' # key, log the new password into the resultant plist's 'SALTED-SHA512' @@ -296,7 +296,7 @@ class Puppet::Provider::NameService::DirectoryService < Puppet::Provider::NameSe if (Puppet::Util::Package.versioncmp(get_macosx_version_major, '10.7') == -1) password_hash = nil password_hash_file = "#{password_hash_dir}/#{guid}" - if File.exists?(password_hash_file) and File.file?(password_hash_file) + if Puppet::FileSystem::File.exist?(password_hash_file) and File.file?(password_hash_file) fail("Could not read password hash file at #{password_hash_file}") if not File.readable?(password_hash_file) f = File.new(password_hash_file) password_hash = f.read @@ -304,7 +304,7 @@ class Puppet::Provider::NameService::DirectoryService < Puppet::Provider::NameSe end password_hash else - if File.exists?("#{users_plist_dir}/#{username}.plist") + if Puppet::FileSystem::File.exist?("#{users_plist_dir}/#{username}.plist") # If a plist exists in /var/db/dslocal/nodes/Default/users, we will # extract the binary plist from the 'ShadowHashData' key, decode the # salted-SHA512 password hash, and then return it. diff --git a/lib/puppet/provider/package/appdmg.rb b/lib/puppet/provider/package/appdmg.rb index 2910c8599..aaf5a7be2 100644 --- a/lib/puppet/provider/package/appdmg.rb +++ b/lib/puppet/provider/package/appdmg.rb @@ -93,7 +93,7 @@ Puppet::Type.type(:package).provide(:appdmg, :parent => Puppet::Provider::Packag end def query - FileTest.exists?("/var/db/.puppet_appdmg_installed_#{@resource[:name]}") ? {:name => @resource[:name], :ensure => :present} : nil + Puppet::FileSystem::File.exist?("/var/db/.puppet_appdmg_installed_#{@resource[:name]}") ? {:name => @resource[:name], :ensure => :present} : nil end def install diff --git a/lib/puppet/provider/package/apple.rb b/lib/puppet/provider/package/apple.rb index 362ba60c7..a603d0783 100755..100644 --- a/lib/puppet/provider/package/apple.rb +++ b/lib/puppet/provider/package/apple.rb @@ -33,7 +33,7 @@ Puppet::Type.type(:package).provide :apple, :parent => Puppet::Provider::Package end def query - FileTest.exists?("/Library/Receipts/#{@resource[:name]}.pkg") ? {:name => @resource[:name], :ensure => :present} : nil + Puppet::FileSystem::File.exist?("/Library/Receipts/#{@resource[:name]}.pkg") ? {:name => @resource[:name], :ensure => :present} : nil end def install diff --git a/lib/puppet/provider/package/apt.rb b/lib/puppet/provider/package/apt.rb index 16618d359..df5cd725a 100755..100644 --- a/lib/puppet/provider/package/apt.rb +++ b/lib/puppet/provider/package/apt.rb @@ -84,7 +84,7 @@ Puppet::Type.type(:package).provide :apt, :parent => :dpkg, :source => :dpkg do # preseeds answers to dpkg-set-selection from the "responsefile" # def run_preseed - if response = @resource[:responsefile] and FileTest.exist?(response) + if response = @resource[:responsefile] and Puppet::FileSystem::File.exist?(response) self.info("Preseeding #{response} to debconf-set-selections") preseed response diff --git a/lib/puppet/provider/package/aptitude.rb b/lib/puppet/provider/package/aptitude.rb index d3ab0da52..d3ab0da52 100755..100644 --- a/lib/puppet/provider/package/aptitude.rb +++ b/lib/puppet/provider/package/aptitude.rb diff --git a/lib/puppet/provider/package/blastwave.rb b/lib/puppet/provider/package/blastwave.rb index 4200e5b2b..fc0698e1a 100755..100644 --- a/lib/puppet/provider/package/blastwave.rb +++ b/lib/puppet/provider/package/blastwave.rb @@ -18,7 +18,7 @@ Puppet::Type.type(:package).provide :blastwave, :parent => :sun, :source => :sun "The pkg-get command is missing; blastwave packaging unavailable" end - unless FileTest.exists?("/var/pkg-get/admin") + unless Puppet::FileSystem::File.exist?("/var/pkg-get/admin") Puppet.notice "It is highly recommended you create '/var/pkg-get/admin'." Puppet.notice "See /var/pkg-get/admin-fullauto" end diff --git a/lib/puppet/provider/package/dpkg.rb b/lib/puppet/provider/package/dpkg.rb index c8528cc4b..b65ef07bf 100755..100644 --- a/lib/puppet/provider/package/dpkg.rb +++ b/lib/puppet/provider/package/dpkg.rb @@ -40,7 +40,7 @@ Puppet::Type.type(:package).provide :dpkg, :parent => Puppet::Provider::Package private # Note: self:: is required here to keep these constants in the context of what will - # eventually become this Puppet:Type::Package::ProviderDpkg class. + # eventually become this Puppet::Type::Package::ProviderDpkg class. self::DPKG_DESCRIPTION_DELIMITER = ':DESC:' self::DPKG_QUERY_FORMAT_STRING = %Q{'${Status} ${Package} ${Version} #{self::DPKG_DESCRIPTION_DELIMITER} ${Description}\\n#{self::DPKG_DESCRIPTION_DELIMITER}\\n'} self::FIELDS_REGEX = %r{^(\S+) +(\S+) +(\S+) (\S+) (\S*) #{self::DPKG_DESCRIPTION_DELIMITER} (.*)$} diff --git a/lib/puppet/provider/package/fink.rb b/lib/puppet/provider/package/fink.rb index 7efa131b9..cbf141462 100755..100644 --- a/lib/puppet/provider/package/fink.rb +++ b/lib/puppet/provider/package/fink.rb @@ -56,7 +56,7 @@ Puppet::Type.type(:package).provide :fink, :parent => :dpkg, :source => :dpkg do # preseeds answers to dpkg-set-selection from the "responsefile" # def run_preseed - if response = @resource[:responsefile] and FileTest.exists?(response) + if response = @resource[:responsefile] and Puppet::FileSystem::File.exist?(response) self.info("Preseeding #{response} to debconf-set-selections") preseed response diff --git a/lib/puppet/provider/package/freebsd.rb b/lib/puppet/provider/package/freebsd.rb index adb72c607..adb72c607 100755..100644 --- a/lib/puppet/provider/package/freebsd.rb +++ b/lib/puppet/provider/package/freebsd.rb diff --git a/lib/puppet/provider/package/gem.rb b/lib/puppet/provider/package/gem.rb index a13bbf35f..a13bbf35f 100755..100644 --- a/lib/puppet/provider/package/gem.rb +++ b/lib/puppet/provider/package/gem.rb diff --git a/lib/puppet/provider/package/macports.rb b/lib/puppet/provider/package/macports.rb index b410d6fc3..b410d6fc3 100755..100644 --- a/lib/puppet/provider/package/macports.rb +++ b/lib/puppet/provider/package/macports.rb diff --git a/lib/puppet/provider/package/msi.rb b/lib/puppet/provider/package/msi.rb index 34da206cc..46617f13f 100644 --- a/lib/puppet/provider/package/msi.rb +++ b/lib/puppet/provider/package/msi.rb @@ -74,22 +74,18 @@ Puppet::Type.type(:package).provide(:msi, :parent => Puppet::Provider::Package) # because of the special quoting we need to do around the MSI # properties to use. command = ['msiexec.exe', '/qn', '/norestart', '/i', shell_quote(resource[:source]), install_options].flatten.compact.join(' ') - execute(command, :failonfail => false, :combine => true) + output = execute(command, :failonfail => false, :combine => true) - check_result(exit_status) + check_result(output.exitstatus) end def uninstall fail("The productcode property is missing.") unless properties[:productcode] command = ['msiexec.exe', '/qn', '/norestart', '/x', properties[:productcode], uninstall_options].flatten.compact.join(' ') - execute(command, :failonfail => false, :combine => true) + output = execute(command, :failonfail => false, :combine => true) - check_result(exit_status) - end - - def exit_status - $CHILD_STATUS.exitstatus + check_result(output.exitstatus) end # (Un)install may "fail" because the package requested a reboot, the system requested a @@ -102,8 +98,6 @@ Puppet::Type.type(:package).provide(:msi, :parent => Puppet::Provider::Package) case hr when 0 # yeah - when 194 - warning("The package requested a reboot to finish the operation.") when 1641 warning("The package #{operation}ed successfully and the system is rebooting now.") when 3010 diff --git a/lib/puppet/provider/package/nim.rb b/lib/puppet/provider/package/nim.rb index 61d4cbcbf..1869cf57f 100644 --- a/lib/puppet/provider/package/nim.rb +++ b/lib/puppet/provider/package/nim.rb @@ -151,10 +151,10 @@ Puppet::Type.type(:package).provide :nim, :parent => :aix, :source => :aix do # I spent a lot of time trying to figure out a solution that didn't # require parsing the `nimclient -o showres` output and was unable to # do so. - HEADER_LINE_REGEX = /^([^\s]+)\s+[^@]+@@(I|R):(\1)\s+[^\s]+$/ - PACKAGE_LINE_REGEX = /^.*@@(I|R):(.*)$/ - RPM_PACKAGE_REGEX = /^(.*)-(.*-\d+) \2$/ - INSTALLP_PACKAGE_REGEX = /^(.*) (.*)$/ + self::HEADER_LINE_REGEX = /^([^\s]+)\s+[^@]+@@(I|R):(\1)\s+[^\s]+$/ + self::PACKAGE_LINE_REGEX = /^.*@@(I|R):(.*)$/ + self::RPM_PACKAGE_REGEX = /^(.*)-(.*-\d+) \2$/ + self::INSTALLP_PACKAGE_REGEX = /^(.*) (.*)$/ # Here is some sample output that shows what the above regexes will be up # against: @@ -205,13 +205,13 @@ Puppet::Type.type(:package).provide :nim, :parent => :aix, :source => :aix do # meant to validate that the header line for the package listing output # looks sane, so we know we're dealing with the kind of output that we # are capable of handling. - unless line.match(HEADER_LINE_REGEX) + unless line.match(self.class::HEADER_LINE_REGEX) self.fail "Unable to parse output from nimclient showres: line does not match expected package header format:\n'#{line}'" end end def parse_installp_package_string(package_string) - unless match = package_string.match(INSTALLP_PACKAGE_REGEX) + unless match = package_string.match(self.class::INSTALLP_PACKAGE_REGEX) self.fail "Unable to parse output from nimclient showres: package string does not match expected installp package string format:\n'#{package_string}'" end package_name = match.captures[0] @@ -220,7 +220,7 @@ Puppet::Type.type(:package).provide :nim, :parent => :aix, :source => :aix do end def parse_rpm_package_string(package_string) - unless match = package_string.match(RPM_PACKAGE_REGEX) + unless match = package_string.match(self.class::RPM_PACKAGE_REGEX) self.fail "Unable to parse output from nimclient showres: package string does not match expected rpm package string format:\n'#{package_string}'" end package_name = match.captures[0] @@ -229,7 +229,7 @@ Puppet::Type.type(:package).provide :nim, :parent => :aix, :source => :aix do end def parse_showres_package_line(line) - unless match = line.match(PACKAGE_LINE_REGEX) + unless match = line.match(self.class::PACKAGE_LINE_REGEX) self.fail "Unable to parse output from nimclient showres: line does not match expected package line format:\n'#{line}'" end diff --git a/lib/puppet/provider/package/openbsd.rb b/lib/puppet/provider/package/openbsd.rb index 75c01ec83..d51bc8f04 100755..100644 --- a/lib/puppet/provider/package/openbsd.rb +++ b/lib/puppet/provider/package/openbsd.rb @@ -54,7 +54,7 @@ Puppet::Type.type(:package).provide :openbsd, :parent => Puppet::Provider::Packa def parse_pkgconf unless @resource[:source] - if File.exist?("/etc/pkg.conf") + if Puppet::FileSystem::File.exist?("/etc/pkg.conf") File.open("/etc/pkg.conf", "rb").readlines.each do |line| if matchdata = line.match(/^installpath\s*=\s*(.+)\s*$/i) @resource[:source] = matchdata[1] diff --git a/lib/puppet/provider/package/opkg.rb b/lib/puppet/provider/package/opkg.rb index 439afeb01..439afeb01 100755..100644 --- a/lib/puppet/provider/package/opkg.rb +++ b/lib/puppet/provider/package/opkg.rb diff --git a/lib/puppet/provider/package/pacman.rb b/lib/puppet/provider/package/pacman.rb index 0b47a4e8b..f811aa5a8 100644 --- a/lib/puppet/provider/package/pacman.rb +++ b/lib/puppet/provider/package/pacman.rb @@ -6,7 +6,7 @@ Puppet::Type.type(:package).provide :pacman, :parent => Puppet::Provider::Packag commands :pacman => "/usr/bin/pacman" # Yaourt is a common AUR helper which, if installed, we can use to query the AUR - commands :yaourt => "/usr/bin/yaourt" if File.exists? '/usr/bin/yaourt' + commands :yaourt => "/usr/bin/yaourt" if Puppet::FileSystem::File.exist? '/usr/bin/yaourt' confine :operatingsystem => :archlinux defaultfor :operatingsystem => :archlinux @@ -14,7 +14,7 @@ Puppet::Type.type(:package).provide :pacman, :parent => Puppet::Provider::Packag # If yaourt is installed, we can make use of it def yaourt? - return File.exists? '/usr/bin/yaourt' + return Puppet::FileSystem::File.exist? '/usr/bin/yaourt' end # Install a package using 'pacman', or 'yaourt' if available. diff --git a/lib/puppet/provider/package/pkgdmg.rb b/lib/puppet/provider/package/pkgdmg.rb index 15c3639de..f14b53158 100644 --- a/lib/puppet/provider/package/pkgdmg.rb +++ b/lib/puppet/provider/package/pkgdmg.rb @@ -113,7 +113,7 @@ Puppet::Type.type(:package).provide :pkgdmg, :parent => Puppet::Provider::Packag end def query - if FileTest.exists?("/var/db/.puppet_pkgdmg_installed_#{@resource[:name]}") + if Puppet::FileSystem::File.exist?("/var/db/.puppet_pkgdmg_installed_#{@resource[:name]}") Puppet.debug "/var/db/.puppet_pkgdmg_installed_#{@resource[:name]} found" return {:name => @resource[:name], :ensure => :present} else diff --git a/lib/puppet/provider/package/pkgutil.rb b/lib/puppet/provider/package/pkgutil.rb index 157066415..c114fa949 100755..100644 --- a/lib/puppet/provider/package/pkgutil.rb +++ b/lib/puppet/provider/package/pkgutil.rb @@ -14,7 +14,7 @@ Puppet::Type.type(:package).provide :pkgutil, :parent => :sun, :source => :sun d end def self.healthcheck() - unless FileTest.exists?("/var/opt/csw/pkgutil/admin") + unless Puppet::FileSystem::File.exist?("/var/opt/csw/pkgutil/admin") Puppet.notice "It is highly recommended you create '/var/opt/csw/pkgutil/admin'." Puppet.notice "See /var/opt/csw/pkgutil" end diff --git a/lib/puppet/provider/package/ports.rb b/lib/puppet/provider/package/ports.rb index 9141e30f5..9141e30f5 100755..100644 --- a/lib/puppet/provider/package/ports.rb +++ b/lib/puppet/provider/package/ports.rb diff --git a/lib/puppet/provider/package/rpm.rb b/lib/puppet/provider/package/rpm.rb index e99e51c7b..6b89eaa62 100755..100644 --- a/lib/puppet/provider/package/rpm.rb +++ b/lib/puppet/provider/package/rpm.rb @@ -3,15 +3,22 @@ require 'puppet/provider/package' # RPM packaging. Should work anywhere that has rpm installed. Puppet::Type.type(:package).provide :rpm, :source => :rpm, :parent => Puppet::Provider::Package do desc "RPM packaging support; should work anywhere with a working `rpm` - binary." + binary. + + This provider supports the `install_options` attribute, which allows + command-line flags to be passed to the RPM binary. Install options should be + specified as an array, where each element is either a string or a + `{'--flag' => 'value'}` hash. (That hash example would be equivalent to a + `'--flag=value'` string; the hash syntax is available as a convenience.)" has_feature :versionable + has_feature :install_options # Note: self:: is required here to keep these constants in the context of what will - # eventually become this Puppet:Type::Package::ProviderRpm class. + # eventually become this Puppet::Type::Package::ProviderRpm class. self::RPM_DESCRIPTION_DELIMITER = ':DESC:' # The query format by which we identify installed packages - self::NEVRA_FORMAT = %Q{'%{NAME} %|EPOCH?{%{EPOCH}}:{0}| %{VERSION} %{RELEASE} %{ARCH} #{self::RPM_DESCRIPTION_DELIMITER} %{SUMMARY}\\n'} + self::NEVRA_FORMAT = %Q{%{NAME} %|EPOCH?{%{EPOCH}}:{0}| %{VERSION} %{RELEASE} %{ARCH} #{self::RPM_DESCRIPTION_DELIMITER} %{SUMMARY}\\n} self::NEVRA_REGEX = %r{^(\S+) (\S+) (\S+) (\S+) (\S+)(?: #{self::RPM_DESCRIPTION_DELIMITER} ?(.*))?$} self::RPM_PACKAGE_NOT_FOUND_REGEX = /package .+ is not installed/ self::NEVRA_FIELDS = [:name, :epoch, :version, :release, :arch, :description] @@ -49,7 +56,7 @@ Puppet::Type.type(:package).provide :rpm, :source => :rpm, :parent => Puppet::Pr # list out all of the packages begin - execpipe("#{command(:rpm)} -qa #{nosignature} #{nodigest} --qf #{self::NEVRA_FORMAT}") { |process| + execpipe("#{command(:rpm)} -qa #{nosignature} #{nodigest} --qf '#{self::NEVRA_FORMAT}'") { |process| # now turn each returned line into a package object process.each_line { |line| hash = nevra_to_hash(line) @@ -107,9 +114,10 @@ Puppet::Type.type(:package).provide :rpm, :source => :rpm, :parent => Puppet::Pr return end - flag = "-i" + flag = ["-i"] flag = ["-U", "--oldpackage"] if @property_hash[:ensure] and @property_hash[:ensure] != :absent + flag = flag + install_options rpm flag, source end @@ -139,8 +147,36 @@ Puppet::Type.type(:package).provide :rpm, :source => :rpm, :parent => Puppet::Pr self.install end + def install_options + join_options(resource[:install_options]) + end + private + # Turns a array of options into flags to be passed to rpm install(8) and + # The options can be passed as a string or hash. Note that passing a hash + # should only be used in case -Dfoo=bar must be passed, + # which can be accomplished with: + # install_options => [ { '-Dfoo' => 'bar' } ] + # Regular flags like '-L' must be passed as a string. + # @param options [Array] + # @return Concatenated list of options + # @api private + def join_options(options) + return [] unless options + + options.collect do |val| + case val + when Hash + val.keys.sort.collect do |k| + "#{k}=#{val[k]}" + end.join(' ') + else + val + end + end + end + # @param line [String] one line of rpm package query information # @return [Hash] of NEVRA_FIELDS strings parsed from package info # if we failed to parse diff --git a/lib/puppet/provider/package/sun.rb b/lib/puppet/provider/package/sun.rb index 5f15ad13e..87a13a2a5 100755..100644 --- a/lib/puppet/provider/package/sun.rb +++ b/lib/puppet/provider/package/sun.rb @@ -15,7 +15,7 @@ Puppet::Type.type(:package).provide :sun, :parent => Puppet::Provider::Package d has_feature :install_options - Namemap = { + self::Namemap = { "PKGINST" => :name, "CATEGORY" => :category, "ARCH" => :platform, @@ -26,8 +26,8 @@ Puppet::Type.type(:package).provide :sun, :parent => Puppet::Provider::Package d } def self.namemap(hash) - Namemap.keys.inject({}) do |hsh,k| - hsh.merge(Namemap[k] => hash[k]) + self::Namemap.keys.inject({}) do |hsh,k| + hsh.merge(self::Namemap[k] => hash[k]) end end diff --git a/lib/puppet/provider/package/sunfreeware.rb b/lib/puppet/provider/package/sunfreeware.rb index 118d36b66..118d36b66 100755..100644 --- a/lib/puppet/provider/package/sunfreeware.rb +++ b/lib/puppet/provider/package/sunfreeware.rb diff --git a/lib/puppet/provider/package/windows.rb b/lib/puppet/provider/package/windows.rb index 7ca022c8e..d4936c89b 100644 --- a/lib/puppet/provider/package/windows.rb +++ b/lib/puppet/provider/package/windows.rb @@ -22,6 +22,7 @@ Puppet::Type.type(:package).provide(:windows, :parent => Puppet::Provider::Packa has_feature :uninstallable has_feature :install_options has_feature :uninstall_options + has_feature :versionable attr_accessor :package @@ -37,9 +38,7 @@ Puppet::Type.type(:package).provide(:windows, :parent => Puppet::Provider::Packa def self.to_hash(pkg) { :name => pkg.name, - # we're not versionable, so we can't set the ensure - # parameter to the currently installed version - :ensure => :installed, + :ensure => pkg.version || :installed, :provider => :windows } end @@ -59,26 +58,22 @@ Puppet::Type.type(:package).provide(:windows, :parent => Puppet::Provider::Packa installer = Puppet::Provider::Package::Windows::Package.installer_class(resource) command = [installer.install_command(resource), install_options].flatten.compact.join(' ') - execute(command, :failonfail => false, :combine => true) + output = execute(command, :failonfail => false, :combine => true) - check_result(exit_status) + check_result(output.exitstatus) end def uninstall command = [package.uninstall_command, uninstall_options].flatten.compact.join(' ') - execute(command, :failonfail => false, :combine => true) + output = execute(command, :failonfail => false, :combine => true) - check_result(exit_status) - end - - def exit_status - $CHILD_STATUS.exitstatus + check_result(output.exitstatus) end # http://msdn.microsoft.com/en-us/library/windows/desktop/aa368542(v=vs.85).aspx - ERROR_SUCCESS = 0 - ERROR_SUCCESS_REBOOT_INITIATED = 1641 - ERROR_SUCCESS_REBOOT_REQUIRED = 3010 + self::ERROR_SUCCESS = 0 + self::ERROR_SUCCESS_REBOOT_INITIATED = 1641 + self::ERROR_SUCCESS_REBOOT_REQUIRED = 3010 # (Un)install may "fail" because the package requested a reboot, the system requested a # reboot, or something else entirely. Reboot requests mean the package was installed @@ -87,13 +82,11 @@ Puppet::Type.type(:package).provide(:windows, :parent => Puppet::Provider::Packa operation = resource[:ensure] == :absent ? 'uninstall' : 'install' case hr - when ERROR_SUCCESS + when self.class::ERROR_SUCCESS # yeah - when 194 - warning("The package requested a reboot to finish the operation.") - when ERROR_SUCCESS_REBOOT_INITIATED + when self.class::ERROR_SUCCESS_REBOOT_INITIATED warning("The package #{operation}ed successfully and the system is rebooting now.") - when ERROR_SUCCESS_REBOOT_REQUIRED + when self.class::ERROR_SUCCESS_REBOOT_REQUIRED warning("The package #{operation}ed successfully, but the system must be rebooted.") else raise Puppet::Util::Windows::Error.new("Failed to #{operation}", hr) diff --git a/lib/puppet/provider/package/windows/package.rb b/lib/puppet/provider/package/windows/package.rb index fd007de6c..e26c6773c 100644 --- a/lib/puppet/provider/package/windows/package.rb +++ b/lib/puppet/provider/package/windows/package.rb @@ -58,7 +58,7 @@ class Puppet::Provider::Package::Windows # REMIND: what about msp, etc MsiPackage when /\.exe"?\Z/i - fail("The source does not exist: '#{resource[:source]}'") unless File.exists?(resource[:source]) + fail("The source does not exist: '#{resource[:source]}'") unless Puppet::FileSystem::File.exist?(resource[:source]) ExePackage else fail("Don't know how to install '#{resource[:source]}'") diff --git a/lib/puppet/provider/package/yum.rb b/lib/puppet/provider/package/yum.rb index dd41aa470..c32052d3d 100644 --- a/lib/puppet/provider/package/yum.rb +++ b/lib/puppet/provider/package/yum.rb @@ -11,7 +11,7 @@ Puppet::Type.type(:package).provide :yum, :parent => :rpm, :source => :rpm do commands :yum => "yum", :rpm => "rpm", :python => "python" - YUMHELPER = File::join(File::dirname(__FILE__), "yumhelper.py") + self::YUMHELPER = File::join(File::dirname(__FILE__), "yumhelper.py") attr_accessor :latest_info @@ -34,7 +34,7 @@ Puppet::Type.type(:package).provide :yum, :parent => :rpm, :source => :rpm do # collect our 'latest' info updates = {} - python(YUMHELPER).each_line do |l| + python(self::YUMHELPER).each_line do |l| l.chomp! next if l.empty? if l[0,4] == "_pkg" diff --git a/lib/puppet/provider/parsedfile.rb b/lib/puppet/provider/parsedfile.rb index 1a13c8df3..1a13c8df3 100755..100644 --- a/lib/puppet/provider/parsedfile.rb +++ b/lib/puppet/provider/parsedfile.rb diff --git a/lib/puppet/provider/port/parsed.rb b/lib/puppet/provider/port/parsed.rb index 5c973b6af..5c973b6af 100755..100644 --- a/lib/puppet/provider/port/parsed.rb +++ b/lib/puppet/provider/port/parsed.rb diff --git a/lib/puppet/provider/service/base.rb b/lib/puppet/provider/service/base.rb index 0d960854f..0d960854f 100755..100644 --- a/lib/puppet/provider/service/base.rb +++ b/lib/puppet/provider/service/base.rb diff --git a/lib/puppet/provider/service/bsd.rb b/lib/puppet/provider/service/bsd.rb index 85e7824fa..a19608a51 100644 --- a/lib/puppet/provider/service/bsd.rb +++ b/lib/puppet/provider/service/bsd.rb @@ -20,13 +20,13 @@ Puppet::Type.type(:service).provide :bsd, :parent => :init do # remove service file from rc.conf.d to disable it def disable rcfile = File.join(rcconf_dir, @model[:name]) - File.delete(rcfile) if File.exists?(rcfile) + File.delete(rcfile) if Puppet::FileSystem::File.exist?(rcfile) end # if the service file exists in rc.conf.d then it's already enabled def enabled? rcfile = File.join(rcconf_dir, @model[:name]) - return :true if File.exists?(rcfile) + return :true if Puppet::FileSystem::File.exist?(rcfile) :false end @@ -34,7 +34,7 @@ Puppet::Type.type(:service).provide :bsd, :parent => :init do # enable service by creating a service file under rc.conf.d with the # proper contents def enable - Dir.mkdir(rcconf_dir) if not File.exists?(rcconf_dir) + Dir.mkdir(rcconf_dir) if not Puppet::FileSystem::File.exist?(rcconf_dir) rcfile = File.join(rcconf_dir, @model[:name]) open(rcfile, 'w') { |f| f << "%s_enable=\"YES\"\n" % @model[:name] } end diff --git a/lib/puppet/provider/service/daemontools.rb b/lib/puppet/provider/service/daemontools.rb index f7784cc61..32c0e0ff7 100644 --- a/lib/puppet/provider/service/daemontools.rb +++ b/lib/puppet/provider/service/daemontools.rb @@ -48,7 +48,7 @@ Puppet::Type.type(:service).provide :daemontools, :parent => :base do def defpath(dummy_argument=:work_arround_for_ruby_GC_bug) unless @defpath ["/var/lib/service", "/etc"].each do |path| - if FileTest.exist?(path) + if Puppet::FileSystem::File.exist?(path) @defpath = path break end @@ -74,7 +74,7 @@ Puppet::Type.type(:service).provide :daemontools, :parent => :base do # or don't contain a run file Dir.entries(path).reject { |e| fullpath = File.join(path, e) - e =~ /^\./ or ! FileTest.directory?(fullpath) or ! FileTest.exist?(File.join(fullpath,"run")) + e =~ /^\./ or ! FileTest.directory?(fullpath) or ! Puppet::FileSystem::File.exist?(File.join(fullpath,"run")) }.collect do |name| new(:name => name, :path => path) end @@ -89,7 +89,7 @@ Puppet::Type.type(:service).provide :daemontools, :parent => :base do def servicedir unless @servicedir ["/service", "/etc/service","/var/lib/svscan"].each do |path| - if FileTest.exist?(path) + if Puppet::FileSystem::File.exist?(path) @servicedir = path break end @@ -142,7 +142,7 @@ Puppet::Type.type(:service).provide :daemontools, :parent => :base do return :true else # the service is enabled if it is linked - return FileTest.symlink?(self.service) ? :true : :false + return Puppet::FileSystem::File.new(self.service).symlink? ? :true : :false end end @@ -152,9 +152,9 @@ Puppet::Type.type(:service).provide :daemontools, :parent => :base do self.setupservice end if self.daemon - if ! FileTest.symlink?(self.service) + if ! Puppet::FileSystem::File.new(self.service).symlink? Puppet.notice "Enabling #{self.service}: linking #{self.daemon} -> #{self.service}" - File.symlink(self.daemon, self.service) + Puppet::FileSystem::File.new(self.daemon).symlink(self.service) end end rescue Puppet::ExecutionFailure @@ -168,9 +168,9 @@ Puppet::Type.type(:service).provide :daemontools, :parent => :base do self.setupservice end if self.daemon - if FileTest.symlink?(self.service) + if Puppet::FileSystem::File.new(self.service).symlink? Puppet.notice "Disabling #{self.service}: removing link #{self.daemon} -> #{self.service}" - File.unlink(self.service) + Puppet::FileSystem::File.unlink(self.service) end end rescue Puppet::ExecutionFailure diff --git a/lib/puppet/provider/service/debian.rb b/lib/puppet/provider/service/debian.rb index 7d14eaa3c..7d14eaa3c 100755..100644 --- a/lib/puppet/provider/service/debian.rb +++ b/lib/puppet/provider/service/debian.rb diff --git a/lib/puppet/provider/service/freebsd.rb b/lib/puppet/provider/service/freebsd.rb index 8cf07bab5..36de850c5 100644 --- a/lib/puppet/provider/service/freebsd.rb +++ b/lib/puppet/provider/service/freebsd.rb @@ -71,7 +71,7 @@ Puppet::Type.type(:service).provide :freebsd, :parent => :init do success = false # Replace in all files, not just in the first found with a match [rcconf, rcconf_local, rcconf_dir + "/#{service}"].each do |filename| - if File.exists?(filename) + if Puppet::FileSystem::File.exist?(filename) s = File.read(filename) if s.gsub!(/^(#{rcvar}(_enable)?)=\"?(YES|NO)\"?/, "\\1=\"#{yesno}\"") File.open(filename, File::WRONLY) { |f| f << s } @@ -87,14 +87,14 @@ Puppet::Type.type(:service).provide :freebsd, :parent => :init do def rc_add(service, rcvar, yesno) append = "\# Added by Puppet\n#{rcvar}_enable=\"#{yesno}\"\n" # First, try the one-file-per-service style - if File.exists?(rcconf_dir) + if Puppet::FileSystem::File.exist?(rcconf_dir) File.open(rcconf_dir + "/#{service}", File::WRONLY | File::APPEND | File::CREAT, 0644) { |f| f << append self.debug("Appended to #{f.path}") } else # Else, check the local rc file first, but don't create it - if File.exists?(rcconf_local) + if Puppet::FileSystem::File.exist?(rcconf_local) File.open(rcconf_local, File::WRONLY | File::APPEND) { |f| f << append self.debug("Appended to #{f.path}") diff --git a/lib/puppet/provider/service/init.rb b/lib/puppet/provider/service/init.rb index 23ad824b2..d46cc8772 100755..100644 --- a/lib/puppet/provider/service/init.rb +++ b/lib/puppet/provider/service/init.rb @@ -99,7 +99,7 @@ Puppet::Type.type(:service).provide :init, :parent => :base do if File.directory?(path) true else - if File.exist?(path) + if Puppet::FileSystem::File.exist?(path) self.debug "Search path #{path} is not a directory" else self.debug "Search path #{path} does not exist" @@ -112,7 +112,7 @@ Puppet::Type.type(:service).provide :init, :parent => :base do def search(name) paths.each do |path| fqname = File.join(path,name) - if File.exist? fqname + if Puppet::FileSystem::File.exist? fqname return fqname else self.debug("Could not find #{name} in #{path}") @@ -121,7 +121,7 @@ Puppet::Type.type(:service).provide :init, :parent => :base do paths.each do |path| fqname_sh = File.join(path,"#{name}.sh") - if File.exist? fqname_sh + if Puppet::FileSystem::File.exist? fqname_sh return fqname_sh else self.debug("Could not find #{name}.sh in #{path}") @@ -154,7 +154,8 @@ Puppet::Type.type(:service).provide :init, :parent => :base do private def self.is_init?(script = initscript) - !File.symlink?(script) || File.readlink(script) != "/lib/init/upstart-job" + file = Puppet::FileSystem::File.new(script) + !file.symlink? || file.readlink != "/lib/init/upstart-job" end end diff --git a/lib/puppet/provider/service/launchd.rb b/lib/puppet/provider/service/launchd.rb index 206676aec..316fdbf46 100644 --- a/lib/puppet/provider/service/launchd.rb +++ b/lib/puppet/provider/service/launchd.rb @@ -112,40 +112,51 @@ Puppet::Type.type(:service).provide :launchd, :parent => :base do array_of_files.compact end + # Get a hash of all launchd plists, keyed by label. This value is cached, but + # the cache will be refreshed if refresh is true. + # + # @api private + def self.make_label_to_path_map(refresh=false) + return @label_to_path_map if @label_to_path_map and not refresh + @label_to_path_map = {} + launchd_paths.each do |path| + return_globbed_list_of_file_paths(path).each do |filepath| + job = read_plist(filepath) + next if job.nil? + if job.has_key?("Label") + @label_to_path_map[job["Label"]] = filepath + else + Puppet.warning("The #{filepath} plist does not contain a 'label' key; " + + "Puppet is skipping it") + next + end + end + end + @label_to_path_map + end + # Sets a class instance variable with a hash of all launchd plist files that # are found on the system. The key of the hash is the job id and the value # is the path to the file. If a label is passed, we return the job id and # path for that specific job. def self.jobsearch(label=nil) - @label_to_path_map ||= {} - if @label_to_path_map.empty? - launchd_paths.each do |path| - return_globbed_list_of_file_paths(path).each do |filepath| - job = read_plist(filepath) - next if job.nil? - if job.has_key?("Label") - if job["Label"] == label - return { label => filepath } - else - @label_to_path_map[job["Label"]] = filepath - end - else - Puppet.warning("The #{filepath} plist does not contain a 'label' key; " + - "Puppet is skipping it") - next - end - end - end - end + by_label = make_label_to_path_map if label - if @label_to_path_map.has_key? label - return { label => @label_to_path_map[label] } + if by_label.has_key? label + return { label => by_label[label] } else - raise Puppet::Error.new("Unable to find launchd plist for job: #{label}") + # try refreshing the map, in case a plist has been added in the interim + by_label = make_label_to_path_map(true) + if by_label.has_key? label + return { label => by_label[label] } + else + raise Puppet::Error, "Unable to find launchd plist for job: #{label}" + end end else - @label_to_path_map + # caller wants the whole map + by_label end end diff --git a/lib/puppet/provider/service/openbsd.rb b/lib/puppet/provider/service/openbsd.rb new file mode 100644 index 000000000..8fe68955e --- /dev/null +++ b/lib/puppet/provider/service/openbsd.rb @@ -0,0 +1,23 @@ +Puppet::Type.type(:service).provide :openbsd, :parent => :init do + + desc "Provider for OpenBSD's rc.d daemon control scripts" + + confine :operatingsystem => :openbsd + defaultfor :operatingsystem => :openbsd + + def self.defpath + ["/etc/rc.d"] + end + + def startcmd + [self.initscript, "-f", :start] + end + + def restartcmd + (@resource[:hasrestart] == :true) && [self.initscript, "-f", :restart] + end + + def statuscmd + [self.initscript, :check] + end +end diff --git a/lib/puppet/provider/service/redhat.rb b/lib/puppet/provider/service/redhat.rb index c1c6401c1..c1c6401c1 100755..100644 --- a/lib/puppet/provider/service/redhat.rb +++ b/lib/puppet/provider/service/redhat.rb diff --git a/lib/puppet/provider/service/runit.rb b/lib/puppet/provider/service/runit.rb index d326b1ebf..8d835d2db 100644 --- a/lib/puppet/provider/service/runit.rb +++ b/lib/puppet/provider/service/runit.rb @@ -42,7 +42,7 @@ Puppet::Type.type(:service).provide :runit, :parent => :daemontools do def defpath(dummy_argument=:work_arround_for_ruby_GC_bug) unless @defpath ["/etc/sv", "/var/lib/service"].each do |path| - if FileTest.exist?(path) + if Puppet::FileSystem::File.exist?(path) @defpath = path break end @@ -57,7 +57,7 @@ Puppet::Type.type(:service).provide :runit, :parent => :daemontools do def servicedir unless @servicedir ["/service", "/etc/service","/var/service"].each do |path| - if FileTest.exist?(path) + if Puppet::FileSystem::File.exist?(path) @servicedir = path break end @@ -105,7 +105,7 @@ Puppet::Type.type(:service).provide :runit, :parent => :daemontools do # before a disable def disable # unlink the daemon symlink to disable it - File.unlink(self.service) if FileTest.symlink?(self.service) + Puppet::FileSystem::File.unlink(self.service) if Puppet::FileSystem::File.new(self.service).symlink? end end diff --git a/lib/puppet/provider/service/smf.rb b/lib/puppet/provider/service/smf.rb index 25ae551de..25ae551de 100755..100644 --- a/lib/puppet/provider/service/smf.rb +++ b/lib/puppet/provider/service/smf.rb diff --git a/lib/puppet/provider/service/src.rb b/lib/puppet/provider/service/src.rb index 8028751ff..8028751ff 100755..100644 --- a/lib/puppet/provider/service/src.rb +++ b/lib/puppet/provider/service/src.rb diff --git a/lib/puppet/provider/service/systemd.rb b/lib/puppet/provider/service/systemd.rb index 1ff070b86..1ff070b86 100755..100644 --- a/lib/puppet/provider/service/systemd.rb +++ b/lib/puppet/provider/service/systemd.rb diff --git a/lib/puppet/provider/service/upstart.rb b/lib/puppet/provider/service/upstart.rb index 04665c971..c7fd58c4e 100755..100644 --- a/lib/puppet/provider/service/upstart.rb +++ b/lib/puppet/provider/service/upstart.rb @@ -71,7 +71,7 @@ Puppet::Type.type(:service).provide :upstart, :parent => :debian do paths.each do |path| service_name = name.match(/^(\S+)/)[1] fqname = File.join(path, service_name + suffix) - if File.exists?(fqname) + if Puppet::FileSystem::File.exist?(fqname) return fqname end @@ -148,7 +148,7 @@ Puppet::Type.type(:service).provide :upstart, :parent => :debian do private def is_upstart?(script = initscript) - File.exists?(script) && script.match(/\/etc\/init\/\S+\.conf/) + Puppet::FileSystem::File.exist?(script) && script.match(/\/etc\/init\/\S+\.conf/) end def version_is_pre_0_6_7 @@ -256,7 +256,7 @@ private end def read_override_file - if File.exists?(overscript) + if Puppet::FileSystem::File.exist?(overscript) read_script_from(overscript) else "" diff --git a/lib/puppet/provider/ssh_authorized_key/parsed.rb b/lib/puppet/provider/ssh_authorized_key/parsed.rb index 44fef458e..cb08b593f 100644 --- a/lib/puppet/provider/ssh_authorized_key/parsed.rb +++ b/lib/puppet/provider/ssh_authorized_key/parsed.rb @@ -41,7 +41,7 @@ Puppet::Type.type(:ssh_authorized_key).provide( end def user - uid = File.stat(target).uid + uid = Puppet::FileSystem::File.new(target).stat.uid Etc.getpwuid(uid).name end @@ -55,7 +55,7 @@ Puppet::Type.type(:ssh_authorized_key).provide( self.class.backup_target(target) Puppet::Util::SUIDManager.asuser(@resource.should(:user)) do - unless File.exist?(dir = File.dirname(target)) + unless Puppet::FileSystem::File.exist?(dir = File.dirname(target)) Puppet.debug "Creating #{dir}" Dir.mkdir(dir, dir_perm) end diff --git a/lib/puppet/provider/sshkey/parsed.rb b/lib/puppet/provider/sshkey/parsed.rb index f874683b7..f874683b7 100755..100644 --- a/lib/puppet/provider/sshkey/parsed.rb +++ b/lib/puppet/provider/sshkey/parsed.rb diff --git a/lib/puppet/provider/user/aix.rb b/lib/puppet/provider/user/aix.rb index 0831f2e26..0831f2e26 100755..100644 --- a/lib/puppet/provider/user/aix.rb +++ b/lib/puppet/provider/user/aix.rb diff --git a/lib/puppet/provider/user/directoryservice.rb b/lib/puppet/provider/user/directoryservice.rb index c9110808d..6a59c4bee 100644 --- a/lib/puppet/provider/user/directoryservice.rb +++ b/lib/puppet/provider/user/directoryservice.rb @@ -238,7 +238,7 @@ Puppet::Type.type(:user).provide :directoryservice do def self.get_sha1(guid) password_hash = nil password_hash_file = "#{password_hash_dir}/#{guid}" - if File.exists?(password_hash_file) and File.file?(password_hash_file) + if Puppet::FileSystem::File.exist?(password_hash_file) and File.file?(password_hash_file) raise Puppet::Error, "Could not read password hash file at #{password_hash_file}" if not File.readable?(password_hash_file) f = File.new(password_hash_file) password_hash = f.read diff --git a/lib/puppet/provider/user/useradd.rb b/lib/puppet/provider/user/useradd.rb index 741d0c1d7..2c6552c73 100644 --- a/lib/puppet/provider/user/useradd.rb +++ b/lib/puppet/provider/user/useradd.rb @@ -160,7 +160,7 @@ Puppet::Type.type(:user).provide :useradd, :parent => Puppet::Provider::NameServ else cmd = [command(:add)] end - if not @resource.should(gid) and Puppet::Util.gid(@resource[:name]) + if not @resource.should(:gid) and Puppet::Util.gid(@resource[:name]) cmd += ["-g", @resource[:name]] end cmd += add_properties diff --git a/lib/puppet/provider/zone/solaris.rb b/lib/puppet/provider/zone/solaris.rb index 6e0d14c31..2cd0e99bb 100644 --- a/lib/puppet/provider/zone/solaris.rb +++ b/lib/puppet/provider/zone/solaris.rb @@ -261,7 +261,7 @@ Puppet::Type.type(:zone).provide(:solaris) do # which makes zoneadmd mount the zone root zoneadm :ready unless File.directory?(zoneetc) - unless File.exists?(sysidcfg) + unless Puppet::FileSystem::File.exist?(sysidcfg) begin File.open(sysidcfg, "w", 0600) do |f| f.puts cfg diff --git a/lib/puppet/rails/benchmark.rb b/lib/puppet/rails/benchmark.rb index 741b6d5bd..8d88280c3 100644 --- a/lib/puppet/rails/benchmark.rb +++ b/lib/puppet/rails/benchmark.rb @@ -52,7 +52,7 @@ module Puppet::Rails::Benchmark file = "/tmp/time_debugging.yaml" - if FileTest.exist?(file) + if Puppet::FileSystem::File.exist?(file) data = YAML.load_file(file) else data = {} diff --git a/lib/puppet/reference/configuration.rb b/lib/puppet/reference/configuration.rb index e7f495403..c1580fe35 100644 --- a/lib/puppet/reference/configuration.rb +++ b/lib/puppet/reference/configuration.rb @@ -14,14 +14,13 @@ config = Puppet::Util::Reference.newreference(:configuration, :depth => 1, :doc # Print the doc string itself begin - str << object.desc.gsub(/\n/, " ") + str << Puppet::Util::Docs.scrub(object.desc) rescue => detail Puppet.log_exception(detail) end str << "\n\n" # Now print the data about the item. - str << "" val = object.default if name.to_s == "vardir" val = "/var/lib/puppet" diff --git a/lib/puppet/reference/indirection.rb b/lib/puppet/reference/indirection.rb index e997c88e9..b1685eca1 100644 --- a/lib/puppet/reference/indirection.rb +++ b/lib/puppet/reference/indirection.rb @@ -10,16 +10,14 @@ reference = Puppet::Util::Reference.newreference :indirection, :doc => "Indirect name = indirection.to_s.capitalize text << "## " + indirection.to_s + "\n\n" - text << ind.doc + "\n\n" - - text << "### Termini\n\n" + text << Puppet::Util::Docs.scrub(ind.doc) + "\n\n" Puppet::Indirector::Terminus.terminus_classes(ind.name).sort { |a,b| a.to_s <=> b.to_s }.each do |terminus| terminus_name = terminus.to_s term_class = Puppet::Indirector::Terminus.terminus_class(ind.name, terminus) if term_class terminus_doc = Puppet::Util::Docs.scrub(term_class.doc) - text << markdown_definitionlist(terminus_name, terminus_doc) + text << markdown_header("`#{terminus_name}` terminus", 3) << terminus_doc << "\n\n" else Puppet.warning "Could not build docs for indirector #{name.inspect}, terminus #{terminus_name.inspect}: could not locate terminus." end @@ -31,7 +29,7 @@ end reference.header = <<HEADER -# About Indirection +## About Indirection Puppet's indirector support pluggable backends (termini) for a variety of key-value stores (indirections). Each indirection type corresponds to a particular Ruby class (the "Indirected Class" below) and values are instances of that class. @@ -40,15 +38,15 @@ The termini can be local (e.g., on-disk files) or remote (e.g., using a REST int An indirector has five methods, which are mapped into HTTP verbs for the REST interface: - * `find(key)` - get a single value (mapped to GET or POST with a singular endpoint) - * `search(key)` - get a list of matching values (mapped to GET with a plural endpoint) - * `head(key)` - return true if the key exists (mapped to HEAD) - * `destroy(key)` - remove the key van value (mapped to DELETE) - * `save(instance)` - write the instance to the store, using the instance's name as the key (mapped to PUT) +* `find(key)` - get a single value (mapped to GET or POST with a singular endpoint) +* `search(key)` - get a list of matching values (mapped to GET with a plural endpoint) +* `head(key)` - return true if the key exists (mapped to HEAD) +* `destroy(key)` - remove the key van value (mapped to DELETE) +* `save(instance)` - write the instance to the store, using the instance's name as the key (mapped to PUT) -These methods are available via the `indirection` class method on the indirected classes. For example:: +These methods are available via the `indirection` class method on the indirected classes. For example: - foo_cert = Puppet::SSL::Certificate.indirection.find('foo.example.com') + foo_cert = Puppet::SSL::Certificate.indirection.find('foo.example.com') At startup, each indirection is configured with a terminus. In most cases, this is the default terminus defined by the indirected class, but it can be overridden by the application or face, or overridden with the `route_file` configuration. @@ -58,12 +56,12 @@ Indirections can also have a cache, represented by a second terminus. This is a write-through cache: modifications are written both to the cache and to the primary terminus. Values fetched from the terminus are written to the cache. -## Interaction with REST +### Interaction with REST REST endpoints have the form `/{environment}/{indirection}/{key}`, where the indirection can be singular or plural, following normal English spelling rules. On the server side, REST responses are generated from the locally-configured endpoints. -## Indirections and Termini +### Indirections and Termini Below is the list of all indirections, their associated terminus classes, and how you select between them. diff --git a/lib/puppet/relationship.rb b/lib/puppet/relationship.rb index 2ffcd298f..ebac97e7e 100755..100644 --- a/lib/puppet/relationship.rb +++ b/lib/puppet/relationship.rb @@ -1,5 +1,3 @@ -#!/usr/bin/env ruby - # subscriptions are permanent associations determining how different # objects react to an event @@ -72,7 +70,7 @@ class Puppet::Relationship "{ #{source} => #{target} }" end - def to_pson_data_hash + def to_data_hash data = { 'source' => source.to_s, 'target' => target.to_s @@ -85,8 +83,13 @@ class Puppet::Relationship data end + # This doesn't include document type as it is part of a catalog + def to_pson_data_hash + to_data_hash + end + def to_pson(*args) - to_pson_data_hash.to_pson(*args) + to_data_hash.to_pson(*args) end def to_s diff --git a/lib/puppet/reports.rb b/lib/puppet/reports.rb index be6e8203f..e219971fb 100755..100644 --- a/lib/puppet/reports.rb +++ b/lib/puppet/reports.rb @@ -72,9 +72,9 @@ class Puppet::Reports instance_loader(:report).loadall loaded_instances(:report).sort { |a,b| a.to_s <=> b.to_s }.each do |name| mod = self.report(name) - docs += "#{name}\n#{"-" * name.to_s.length}\n" + docs << "#{name}\n#{"-" * name.to_s.length}\n" - docs += Puppet::Util::Docs.scrub(mod.doc) + "\n\n" + docs << Puppet::Util::Docs.scrub(mod.doc) << "\n\n" end docs diff --git a/lib/puppet/reports/rrdgraph.rb b/lib/puppet/reports/rrdgraph.rb index f283f7c27..e55d653c4 100644 --- a/lib/puppet/reports/rrdgraph.rb +++ b/lib/puppet/reports/rrdgraph.rb @@ -114,7 +114,7 @@ Puppet::Reports.register_report(:rrdgraph) do metric.graph end - mkhtml unless FileTest.exists?(File.join(hostdir, "index.html")) + mkhtml unless Puppet::FileSystem::File.exist?(File.join(hostdir, "index.html")) end # Unfortunately, RRD does not deal well with changing lists of values, diff --git a/lib/puppet/reports/store.rb b/lib/puppet/reports/store.rb index 7d318ffca..27d649355 100644 --- a/lib/puppet/reports/store.rb +++ b/lib/puppet/reports/store.rb @@ -17,7 +17,7 @@ Puppet::Reports.register_report(:store) do dir = File.join(Puppet[:reportdir], host) - if ! FileTest.exists?(dir) + if ! Puppet::FileSystem::File.exist?(dir) FileUtils.mkdir_p(dir) FileUtils.chmod_R(0750, dir) end @@ -54,11 +54,11 @@ Puppet::Reports.register_report(:store) do dir = File.join(Puppet[:reportdir], host) - if File.exists?(dir) + if Puppet::FileSystem::File.exist?(dir) Dir.entries(dir).each do |file| next if ['.','..'].include?(file) file = File.join(dir, file) - File.unlink(file) if File.file?(file) + Puppet::FileSystem::File.unlink(file) if File.file?(file) end Dir.rmdir(dir) end diff --git a/lib/puppet/reports/tagmail.rb b/lib/puppet/reports/tagmail.rb index 528b6fa38..a07f98986 100644 --- a/lib/puppet/reports/tagmail.rb +++ b/lib/puppet/reports/tagmail.rb @@ -108,7 +108,7 @@ Puppet::Reports.register_report(:tagmail) do # Process the report. This just calls the other associated messages. def process - unless FileTest.exists?(Puppet[:tagmap]) + unless Puppet::FileSystem::File.exist?(Puppet[:tagmap]) Puppet.notice "Cannot send tagmail report; no tagmap file #{Puppet[:tagmap]}" return end @@ -158,7 +158,7 @@ Puppet::Reports.register_report(:tagmail) do p.puts "From: #{Puppet[:reportfrom]}" p.puts "Subject: Puppet Report for #{self.host}" p.puts "To: " + emails.join(", ") - + p.puts p.puts messages end end diff --git a/lib/puppet/resource.rb b/lib/puppet/resource.rb index eae59f389..d4eac6f6d 100644 --- a/lib/puppet/resource.rb +++ b/lib/puppet/resource.rb @@ -56,7 +56,7 @@ class Puppet::Resource "#{@type}[#{@title}]#{to_hash.inspect}" end - def to_pson_data_hash + def to_data_hash data = ([:type, :title, :tags] + ATTRIBUTES).inject({}) do |hash, param| next hash unless value = self.send(param) hash[param.to_s] = value @@ -80,6 +80,11 @@ class Puppet::Resource data end + # This doesn't include document type as it is part of a catalog + def to_pson_data_hash + to_data_hash + end + def self.value_to_pson_data(value) if value.is_a? Array value.map{|v| value_to_pson_data(v) } @@ -102,8 +107,20 @@ class Puppet::Resource end end + YAML_ATTRIBUTES = [:@file, :@line, :@exported, :@type, :@title, :@tags, :@parameters] + + # Explicitly list the instance variables that should be serialized when + # converting to YAML. + # + # @api private + # @return [Array<Symbol>] The intersection of our explicit variable list and + # all of the instance variables defined on this class. + def to_yaml_properties + YAML_ATTRIBUTES & super + end + def to_pson(*args) - to_pson_data_hash.to_pson(*args) + to_data_hash.to_pson(*args) end # Proxy these methods to the parameters hash. It's likely they'll @@ -328,10 +345,7 @@ class Puppet::Resource result = scope.compiler.injector.lookup(scope, name) end if result.nil? - Puppet::DataBinding.indirection.find( - name, - :environment => scope.environment.to_s, - :variables => Puppet::DataBinding::Variables.new(scope)) + lookup_with_databinding(name, scope) else result end @@ -339,6 +353,19 @@ class Puppet::Resource private :lookup_external_default_for + def lookup_with_databinding(name, scope) + begin + Puppet::DataBinding.indirection.find( + name, + :environment => scope.environment.to_s, + :variables => scope) + rescue Puppet::DataBinding::LookupError => e + raise Puppet::Error.new("Error from DataBinding '#{Puppet[:data_binding_terminus]}' while looking up '#{name}': #{e.message}", e) + end + end + + private :lookup_with_databinding + def set_default_parameters(scope) return [] unless resource_type and resource_type.respond_to?(:arguments) @@ -362,8 +389,39 @@ class Puppet::Resource end.compact end - def to_resource - self + def copy_as_resource + result = Puppet::Resource.new(type, title) + + to_hash.each do |p, v| + if v.is_a?(Puppet::Resource) + v = Puppet::Resource.new(v.type, v.title) + elsif v.is_a?(Array) + # flatten resource references arrays + v = v.flatten if v.flatten.find { |av| av.is_a?(Puppet::Resource) } + v = v.collect do |av| + av = Puppet::Resource.new(av.type, av.title) if av.is_a?(Puppet::Resource) + av + end + end + + # If the value is an array with only one value, then + # convert it to a single value. This is largely so that + # the database interaction doesn't have to worry about + # whether it returns an array or a string. + result[p] = if v.is_a?(Array) and v.length == 1 + v[0] + else + v + end + end + + result.file = self.file + result.line = self.line + result.exported = self.exported + result.virtual = self.virtual + result.tag(*self.tags) + + result end def valid_parameter?(name) diff --git a/lib/puppet/resource/catalog.rb b/lib/puppet/resource/catalog.rb index 3120d3ff7..61b4f0b81 100644 --- a/lib/puppet/resource/catalog.rb +++ b/lib/puppet/resource/catalog.rb @@ -365,19 +365,23 @@ class Puppet::Resource::Catalog < Puppet::Graph::SimpleGraph result.add_edge(edge) end + def to_data_hash + { + 'tags' => tags, + 'name' => name, + 'version' => version, + 'environment' => environment.to_s, + 'resources' => @resources.collect { |v| @resource_table[v].to_pson_data_hash }, + 'edges' => edges. collect { |e| e.to_pson_data_hash }, + 'classes' => classes + } + end + PSON.register_document_type('Catalog',self) def to_pson_data_hash { 'document_type' => 'Catalog', - 'data' => { - 'tags' => tags, - 'name' => name, - 'version' => version, - 'environment' => environment.to_s, - 'resources' => @resources.collect { |v| @resource_table[v].to_pson_data_hash }, - 'edges' => edges. collect { |e| e.to_pson_data_hash }, - 'classes' => classes - }, + 'data' => to_data_hash, 'metadata' => { 'api_version' => 1 } @@ -465,7 +469,7 @@ class Puppet::Resource::Catalog < Puppet::Graph::SimpleGraph # Verify that the given resource isn't declared elsewhere. def fail_on_duplicate_type_and_title(resource) - # Short-curcuit the common case, + # Short-circuit the common case, return unless existing_resource = @resource_table[title_key_for_ref(resource.ref)] # If we've gotten this far, it's a real conflict @@ -492,22 +496,11 @@ class Puppet::Resource::Catalog < Puppet::Graph::SimpleGraph next if virtual_not_exported?(resource) next if block_given? and yield resource - #This is hackity hack for 1094 - #Aliases aren't working in the ral catalog because the current instance of the resource - #has a reference to the catalog being converted. . . So, give it a reference to the new one - #problem solved. . . - if resource.class == Puppet::Resource - resource = resource.dup - resource.catalog = result - elsif resource.is_a?(Puppet::Parser::Resource) - resource = resource.to_resource - resource.catalog = result - end + newres = resource.copy_as_resource + newres.catalog = result - if resource.is_a?(Puppet::Resource) and convert.to_s == "to_resource" - newres = resource - else - newres = resource.send(convert) + if convert != :to_resource + newres = newres.to_ral end # We can't guarantee that resources don't munge their names diff --git a/lib/puppet/resource/status.rb b/lib/puppet/resource/status.rb index 6bfe73b2b..eae5aeed9 100644 --- a/lib/puppet/resource/status.rb +++ b/lib/puppet/resource/status.rb @@ -1,10 +1,12 @@ require 'time' +require 'puppet/network/format_support' module Puppet class Resource class Status include Puppet::Util::Tagging include Puppet::Util::Logging + include Puppet::Network::FormatSupport attr_accessor :resource, :node, :file, :line, :current_values, :status, :evaluation_time @@ -65,7 +67,7 @@ module Puppet # will always be accompanied by an event with some explanatory power. This # is useful for reporting/diagnostics/etc. So synthesize an event here # with the exception detail as the message. - add_event(@real_resource.event(:status => "failure", :message => detail.to_s)) + add_event(@real_resource.event(:name => :resource_error, :status => "failure", :message => detail.to_s)) end def initialize(resource) @@ -100,7 +102,7 @@ module Puppet @evaluation_time = data['evaluation_time'] @change_count = data['change_count'] @out_of_sync_count = data['out_of_sync_count'] - @tags = data['tags'] + @tags = Puppet::Util::TagSet.new(data['tags']) @time = data['time'] @time = Time.parse(@time) if @time.is_a? String @out_of_sync = data['out_of_sync'] @@ -113,7 +115,7 @@ module Puppet end end - def to_pson + def to_data_hash { 'title' => @title, 'file' => @file, @@ -131,7 +133,11 @@ module Puppet 'change_count' => @change_count, 'out_of_sync_count' => @out_of_sync_count, 'events' => @events, - }.to_pson + } + end + + def to_pson(*args) + to_data_hash.to_pson(*args) end def to_yaml_properties diff --git a/lib/puppet/run.rb b/lib/puppet/run.rb index 762244119..1d89304be 100644 --- a/lib/puppet/run.rb +++ b/lib/puppet/run.rb @@ -94,11 +94,15 @@ class Puppet::Run new(options) end - def to_pson + def to_data_hash { :options => @options, :background => @background, :status => @status - }.to_pson + } + end + + def to_pson(*args) + to_data_hash.to_pson(*args) end end diff --git a/lib/puppet/settings.rb b/lib/puppet/settings.rb index b47039b66..ca017ae1e 100644 --- a/lib/puppet/settings.rb +++ b/lib/puppet/settings.rb @@ -1,5 +1,4 @@ require 'puppet' -require 'sync' require 'getoptlong' require 'puppet/util/watched_file' require 'puppet/util/command_line/puppet_option_parser' @@ -18,6 +17,8 @@ class Puppet::Settings require 'puppet/settings/boolean_setting' require 'puppet/settings/terminus_setting' require 'puppet/settings/duration_setting' + require 'puppet/settings/priority_setting' + require 'puppet/settings/autosign_setting' require 'puppet/settings/config_file' require 'puppet/settings/value_translator' @@ -74,9 +75,6 @@ class Puppet::Settings @created = [] @searchpath = nil - # Mutex-like thing to protect @values - @sync = Sync.new - # Keep track of set values. @values = Hash.new { |hash, key| hash[key] = {} } @@ -95,6 +93,12 @@ class Puppet::Settings @config_file_parser = Puppet::Settings::ConfigFile.new(@translate) end + # @param name [Symbol] The name of the setting to fetch + # @return [Puppet::Settings::BaseSetting] The setting object + def setting(name) + @config[name] + end + # Retrieve a config value def [](param) value(param) @@ -135,9 +139,7 @@ class Puppet::Settings # Remove all set values, potentially skipping cli values. def clear - @sync.synchronize do - unsafe_clear - end + unsafe_clear end # Remove all set values, potentially skipping cli values. @@ -170,9 +172,7 @@ class Puppet::Settings # this method must be called to clear out the caches so that updated # objects will be returned. def flush_cache - @sync.synchronize do - unsafe_flush_cache - end + unsafe_flush_cache end def unsafe_flush_cache @@ -507,9 +507,7 @@ class Puppet::Settings # Parse the configuration file. Just provides thread safety. def parse_config_files - @sync.synchronize do - unsafe_parse(which_configuration_file) - end + unsafe_parse(which_configuration_file) call_hooks_deferred_to_application_initialization :ignore_interpolation_dependency_errors => true end @@ -552,7 +550,7 @@ class Puppet::Settings def unsafe_parse(file) # build up a single data structure that contains the values from all of the parsed files. data = {} - if FileTest.exist?(file) + if Puppet::FileSystem::File.exist?(file) begin file_data = parse_file(file) @@ -602,7 +600,11 @@ class Puppet::Settings # This results in extra work, but so few of the settings # will have associated hooks that it ends up being less work this # way overall. - setting.handle(self.value(setting.name, env)) + if setting.call_hook_on_initialize? + @hooks_to_call_on_application_initialization << setting + else + setting.handle(self.value(setting.name, env)) + end break end end @@ -636,6 +638,8 @@ class Puppet::Settings :terminus => TerminusSetting, :duration => DurationSetting, :enum => EnumSetting, + :priority => PrioritySetting, + :autosign => AutosignSetting, } # Create a new setting. The value is passed in because it's used to determine @@ -692,7 +696,7 @@ class Puppet::Settings return @files if @files @files = [] [main_config_file, user_config_file].each do |path| - if FileTest.exist?(path) + if Puppet::FileSystem::File.exist?(path) @files << Puppet::Util::WatchedFile.new(path) end end @@ -713,11 +717,9 @@ class Puppet::Settings def reuse return unless defined?(@used) - @sync.synchronize do # yay, thread-safe - new = @used - @used = [] - self.use(*new) - end + new = @used + @used = [] + self.use(*new) end # The order in which to search for values. @@ -788,12 +790,8 @@ class Puppet::Settings setting.handle(value) if setting.has_hook? and not options[:dont_trigger_handles] - @sync.synchronize do # yay, thread-safe - - @values[type][param] = value - unsafe_flush_cache - - end + @values[type][param] = value + unsafe_flush_cache value end @@ -839,7 +837,7 @@ class Puppet::Settings def define_settings(section, defs) section = section.to_sym call = [] - defs.each { |name, hash| + defs.each do |name, hash| raise ArgumentError, "setting definition for '#{name}' is not a hash!" unless hash.is_a? Hash name = name.to_sym @@ -858,9 +856,12 @@ class Puppet::Settings # Collect the settings that need to have their hooks called immediately. # We have to collect them so that we can be sure we're fully initialized before # the hook is called. - call << tryconfig if tryconfig.call_hook_on_define? - @hooks_to_call_on_application_initialization << tryconfig if tryconfig.call_hook_on_initialize? - } + if tryconfig.call_hook_on_define? + call << tryconfig + elsif tryconfig.call_hook_on_initialize? + @hooks_to_call_on_application_initialization << tryconfig + end + end call.each { |setting| setting.handle(self.value(setting.name)) } end @@ -927,29 +928,27 @@ Generated on #{Time.now}. # you can 'use' a section as many times as you want. def use(*sections) sections = sections.collect { |s| s.to_sym } - @sync.synchronize do # yay, thread-safe - sections = sections.reject { |s| @used.include?(s) } + sections = sections.reject { |s| @used.include?(s) } - return if sections.empty? + return if sections.empty? - begin - catalog = to_catalog(*sections).to_ral - rescue => detail - Puppet.log_and_raise(detail, "Could not create resources for managing Puppet's files and directories in sections #{sections.inspect}: #{detail}") - end + begin + catalog = to_catalog(*sections).to_ral + rescue => detail + Puppet.log_and_raise(detail, "Could not create resources for managing Puppet's files and directories in sections #{sections.inspect}: #{detail}") + end - catalog.host_config = false - catalog.apply do |transaction| - if transaction.any_failed? - report = transaction.report - failures = report.logs.find_all { |log| log.level == :err } - raise "Got #{failures.length} failure(s) while initializing: #{failures.collect { |l| l.to_s }.join("; ")}" - end + catalog.host_config = false + catalog.apply do |transaction| + if transaction.any_failed? + report = transaction.report + failures = report.logs.find_all { |log| log.level == :err } + raise "Got #{failures.length} failure(s) while initializing: #{failures.collect { |l| l.to_s }.join("; ")}" end - - sections.each { |s| @used << s } - @used.uniq! end + + sections.each { |s| @used << s } + @used.uniq! end def valid?(param) @@ -974,9 +973,7 @@ Generated on #{Time.now}. each_source(environment) do |source| # Look for the value. We have to test the hash for whether # it exists, because the value might be false. - @sync.synchronize do - return @values[source][param] if @values[source].include?(param) - end + return @values[source][param] if @values[source].include?(param) end return nil end @@ -1033,70 +1030,6 @@ Generated on #{Time.now}. val end - # Open a file with the appropriate user, group, and mode - def write(default, *args, &bloc) - obj = get_config_file_default(default) - writesub(default, value(obj.name), *args, &bloc) - end - - # Open a non-default file under a default dir with the appropriate user, - # group, and mode - def writesub(default, file, *args, &bloc) - obj = get_config_file_default(default) - chown = nil - if Puppet.features.root? - chown = [obj.owner, obj.group] - else - chown = [nil, nil] - end - - Puppet::Util::SUIDManager.asuser(*chown) do - mode = obj.mode ? obj.mode.to_i : 0640 - args << "w" if args.empty? - - args << mode - - # Update the umask to make non-executable files - Puppet::Util.withumask(File.umask ^ 0111) do - File.open(file, *args) do |file| - yield file - end - end - end - end - - def readwritelock(default, *args, &bloc) - file = value(get_config_file_default(default).name) - tmpfile = file + ".tmp" - sync = Sync.new - raise Puppet::DevError, "Cannot create #{file}; directory #{File.dirname(file)} does not exist" unless FileTest.directory?(File.dirname(tmpfile)) - - sync.synchronize(Sync::EX) do - File.open(file, ::File::CREAT|::File::RDWR, 0600) do |rf| - rf.lock_exclusive do - if File.exist?(tmpfile) - raise Puppet::Error, ".tmp file already exists for #{file}; Aborting locked write. Check the .tmp file and delete if appropriate" - end - - # If there's a failure, remove our tmpfile - begin - writesub(default, tmpfile, *args, &bloc) - rescue - File.unlink(tmpfile) if FileTest.exist?(tmpfile) - raise - end - - begin - File.rename(tmpfile, file) - rescue => detail - Puppet.err "Could not rename #{file} to #{tmpfile}: #{detail}" - File.unlink(tmpfile) if FileTest.exist?(tmpfile) - end - end - end - end - end - private def get_config_file_default(default) @@ -1166,9 +1099,7 @@ Generated on #{Time.now}. def set_metadata(meta) meta.each do |var, values| values.each do |param, value| - @sync.synchronize do # yay, thread-safe - @config[var].send(param.to_s + "=", value) - end + @config[var].send(param.to_s + "=", value) end end end @@ -1177,11 +1108,9 @@ Generated on #{Time.now}. # # @return nil def clear_everything_for_tests() - @sync.synchronize do - unsafe_clear(true, true) - @global_defaults_initialized = false - @app_defaults_initialized = false - end + unsafe_clear(true, true) + @global_defaults_initialized = false + @app_defaults_initialized = false end private :clear_everything_for_tests diff --git a/lib/puppet/settings/autosign_setting.rb b/lib/puppet/settings/autosign_setting.rb new file mode 100644 index 000000000..f13824ecf --- /dev/null +++ b/lib/puppet/settings/autosign_setting.rb @@ -0,0 +1,20 @@ +require 'puppet/settings/base_setting' + +class Puppet::Settings::AutosignSetting < Puppet::Settings::BaseSetting + + def type + :autosign + end + + def munge(value) + if ['true', true].include? value + true + elsif ['false', false, nil].include? value + false + elsif Puppet::Util.absolute_path?(value) + value + else + raise Puppet::Settings::ValidationError, "Invalid autosign value #{value}: must be 'true'/'false' or an absolute path" + end + end +end diff --git a/lib/puppet/settings/base_setting.rb b/lib/puppet/settings/base_setting.rb index 92c904e90..ac38b35f3 100644 --- a/lib/puppet/settings/base_setting.rb +++ b/lib/puppet/settings/base_setting.rb @@ -2,17 +2,13 @@ require 'puppet/settings/errors' # The base setting type class Puppet::Settings::BaseSetting - attr_accessor :name, :section, :default, :call_on_define, :call_hook - attr_reader :desc, :short + attr_accessor :name, :desc, :section, :default, :call_on_define, :call_hook + attr_reader :short def self.available_call_hook_values [:on_define_and_write, :on_initialize_and_write, :on_write_only] end - def desc=(value) - @desc = value.gsub(/^\s*/, '') - end - def call_on_define Puppet.deprecation_warning "call_on_define has been deprecated. Please use call_hook_on_define?" call_hook_on_define? @@ -130,10 +126,12 @@ class Puppet::Settings::BaseSetting # Convert the object to a config statement. def to_config - str = @desc.gsub(/^/, "# ") + "\n" + require 'puppet/util/docs' + # Scrub any funky indentation; comment out description. + str = Puppet::Util::Docs.scrub(@desc).gsub(/^/, "# ") + "\n" # Add in a statement about the default. - str += "# The default value is '#{default(true)}'.\n" if default(true) + str << "# The default value is '#{default(true)}'.\n" if default(true) # If the value has not been overridden, then print it out commented # and unconverted, so it's clear that that's the default and how it @@ -146,8 +144,9 @@ class Puppet::Settings::BaseSetting line = "# #{@name} = #{@default}" end - str += line + "\n" + str << (line + "\n") + # Indent str.gsub(/^/, " ") end diff --git a/lib/puppet/settings/directory_setting.rb b/lib/puppet/settings/directory_setting.rb index 2a9472f92..9f67f96ab 100644 --- a/lib/puppet/settings/directory_setting.rb +++ b/lib/puppet/settings/directory_setting.rb @@ -2,4 +2,12 @@ class Puppet::Settings::DirectorySetting < Puppet::Settings::FileSetting def type :directory end + + # @api private + def open_file(filename, option = 'r', &block) + file = Puppet::FileSystem::File.new(filename) + controlled_access do |mode| + file.open(mode, option, &block) + end + end end diff --git a/lib/puppet/settings/file_setting.rb b/lib/puppet/settings/file_setting.rb index 265bc0613..243805edb 100644 --- a/lib/puppet/settings/file_setting.rb +++ b/lib/puppet/settings/file_setting.rb @@ -128,7 +128,7 @@ class Puppet::Settings::FileSetting < Puppet::Settings::StringSetting # Make sure the paths are fully qualified. path = File.expand_path(path) - return nil unless type == :directory or create_files? or File.exist?(path) + return nil unless type == :directory or create_files? or Puppet::FileSystem::File.exist?(path) return nil if path =~ /^\/dev/ or path =~ /^[A-Z]:\/dev/i resource = Puppet::Resource.new(:file, path) @@ -178,8 +178,42 @@ class Puppet::Settings::FileSetting < Puppet::Settings::StringSetting } end + # @api private + def exclusive_open(option = 'r', &block) + controlled_access do |mode| + file.exclusive_open(mode, option, &block) + end + end + + # @api private + def open(option = 'r', &block) + controlled_access do |mode| + file.open(mode, option, &block) + end + end + private + def file + Puppet::FileSystem::File.new(value) + end + def unknown_value(parameter, value) raise SettingError, "The #{parameter} parameter for the setting '#{name}' must be either 'root' or 'service', not '#{value}'" end + + def controlled_access(&block) + chown = nil + if Puppet.features.root? + chown = [owner, group] + else + chown = [nil, nil] + end + + Puppet::Util::SUIDManager.asuser(*chown) do + # Update the umask to make non-executable files + Puppet::Util.withumask(File.umask ^ 0111) do + yield mode ? mode.to_i : 0640 + end + end + end end diff --git a/lib/puppet/settings/priority_setting.rb b/lib/puppet/settings/priority_setting.rb new file mode 100644 index 000000000..707b8ab82 --- /dev/null +++ b/lib/puppet/settings/priority_setting.rb @@ -0,0 +1,42 @@ +require 'puppet/settings/base_setting' + +# A setting that represents a scheduling priority, and evaluates to an +# OS-specific priority level. +class Puppet::Settings::PrioritySetting < Puppet::Settings::BaseSetting + PRIORITY_MAP = + if Puppet::Util::Platform.windows? + require 'win32/process' + { + :high => Process::HIGH_PRIORITY_CLASS, + :normal => Process::NORMAL_PRIORITY_CLASS, + :low => Process::BELOW_NORMAL_PRIORITY_CLASS, + :idle => Process::IDLE_PRIORITY_CLASS + } + else + { + :high => -10, + :normal => 0, + :low => 10, + :idle => 19 + } + end + + def type + :priority + end + + def munge(value) + return unless value + + case + when value.is_a?(Integer) + value + when (value.is_a?(String) and value =~ /\d+/) + value.to_i + when (value.is_a?(String) and PRIORITY_MAP[value.to_sym]) + PRIORITY_MAP[value.to_sym] + else + raise Puppet::Settings::ValidationError, "Invalid priority format '#{value.inspect}' for parameter: #{@name}" + end + end +end diff --git a/lib/puppet/ssl.rb b/lib/puppet/ssl.rb index 8f71ba8a4..596feb933 100644 --- a/lib/puppet/ssl.rb +++ b/lib/puppet/ssl.rb @@ -5,4 +5,8 @@ require 'openssl' module Puppet::SSL # :nodoc: CA_NAME = "ca" require 'puppet/ssl/host' + require 'puppet/ssl/oids' + require 'puppet/ssl/validator' + require 'puppet/ssl/validator/no_validator' + require 'puppet/ssl/validator/default_validator' end diff --git a/lib/puppet/ssl/certificate.rb b/lib/puppet/ssl/certificate.rb index a49267ac7..a8ed118fa 100644 --- a/lib/puppet/ssl/certificate.rb +++ b/lib/puppet/ssl/certificate.rb @@ -44,4 +44,22 @@ DOC def unmunged_name self.class.name_from_subject(content.subject) end + + # Any extensions registered with custom OIDs as defined in module + # Puppet::SSL::Oids may be looked up here. + # + # A cert with a 'pp_uuid' extension having the value 'abcd' would return: + # + # [{ 'oid' => 'pp_uuid', 'value' => 'abcd'}] + # + # @return [Array<Hash{String => String}>] An array of two element hashes, + # with key/value pairs for the extension's oid, and its value. + def custom_extensions + custom_exts = content.extensions.select do |ext| + Puppet::SSL::Oids.subtree_of?('ppRegCertExt', ext.oid) or + Puppet::SSL::Oids.subtree_of?('ppPrivCertExt', ext.oid) + end + + custom_exts.map { |ext| {'oid' => ext.oid, 'value' => ext.value} } + end end diff --git a/lib/puppet/ssl/certificate_authority.rb b/lib/puppet/ssl/certificate_authority.rb index 83ff6a114..3ff4d5eb5 100644 --- a/lib/puppet/ssl/certificate_authority.rb +++ b/lib/puppet/ssl/certificate_authority.rb @@ -1,4 +1,3 @@ -require 'monitor' require 'puppet/ssl/host' require 'puppet/ssl/certificate_request' require 'puppet/ssl/certificate_signer' @@ -26,10 +25,9 @@ class Puppet::SSL::CertificateAuthority require 'puppet/ssl/inventory' require 'puppet/ssl/certificate_revocation_list' require 'puppet/ssl/certificate_authority/interface' + require 'puppet/ssl/certificate_authority/autosign_command' require 'puppet/network/authstore' - extend MonitorMixin - class CertificateVerificationError < RuntimeError attr_accessor :error_code @@ -39,9 +37,7 @@ class Puppet::SSL::CertificateAuthority end def self.singleton_instance - synchronize do - @singleton_instance ||= new - end + @singleton_instance ||= new end class CertificateSigningError < RuntimeError @@ -53,67 +49,56 @@ class Puppet::SSL::CertificateAuthority end def self.ca? - return false unless Puppet[:ca] - return false unless Puppet.run_mode.master? - true + # running as ca? - ensure boolean answer + !!(Puppet[:ca] && Puppet.run_mode.master?) end - # If this process can function as a CA, then return a singleton - # instance. + # If this process can function as a CA, then return a singleton instance. def self.instance - return nil unless ca? - - singleton_instance + ca? ? singleton_instance : nil end attr_reader :name, :host - # Create and run an applicator. I wanted to build an interface where you could do - # something like 'ca.apply(:generate).to(:all) but I don't think it's really possible. - def apply(method, options) - raise ArgumentError, "You must specify the hosts to apply to; valid values are an array or the symbol :all" unless options[:to] - applier = Interface.new(method, options) - applier.apply(self) - end - - # If autosign is configured, then autosign all CSRs that match our configuration. - def autosign - return unless auto = autosign? - - store = nil - store = autosign_store(auto) if auto != true - - Puppet::SSL::CertificateRequest.indirection.search("*").each do |csr| - if auto == true or store.allowed?(csr.name, "127.1.1.1") - Puppet.info "Autosigning #{csr.name}" - sign(csr.name) - end + # If autosign is configured, autosign the csr we are passed. + # @param csr [Puppet::SSL::CertificateRequest] The csr to sign. + # @return [Void] + # @api private + def autosign(csr) + if autosign?(csr) + Puppet.info "Autosigning #{csr.name}" + sign(csr.name) end end - # Do we autosign? This returns true, false, or a filename. - def autosign? + # Determine if a CSR can be autosigned by the autosign store or autosign command + # + # @param csr [Puppet::SSL::CertificateRequest] The CSR to check + # @return [true, false] + # @api private + def autosign?(csr) auto = Puppet[:autosign] - return false if ['false', false].include?(auto) - return true if ['true', true].include?(auto) - raise ArgumentError, "The autosign configuration '#{auto}' must be a fully qualified file" unless Puppet::Util.absolute_path?(auto) - FileTest.exist?(auto) && auto - end - - # Create an AuthStore for autosigning. - def autosign_store(file) - auth = Puppet::Network::AuthStore.new - File.readlines(file).each do |line| - next if line =~ /^\s*#/ - next if line =~ /^\s*$/ - auth.allow(line.chomp) - end + decider = case auto + when 'false', false, nil + AutosignNever.new + when 'true', true + AutosignAlways.new + else + file = Puppet::FileSystem::File.new(auto) + if file.executable? + Puppet::SSL::CertificateAuthority::AutosignCommand.new(auto) + elsif file.exist? + AutosignConfig.new(file) + else + AutosignNever.new + end + end - auth + decider.allowed?(csr) end - # Retrieve (or create, if necessary) the certificate revocation list. + # Retrieves (or creates, if necessary) the certificate revocation list. def crl unless defined?(@crl) unless @crl = Puppet::SSL::CertificateRevocationList.indirection.find(Puppet::SSL::CA_NAME) @@ -125,12 +110,12 @@ class Puppet::SSL::CertificateAuthority @crl end - # Delegate this to our Host class. + # Delegates this to our Host class. def destroy(name) Puppet::SSL::Host.destroy(name) end - # Generate a new certificate. + # Generates a new certificate. # @return Puppet::SSL::Certificate def generate(name, options = {}) raise ArgumentError, "A Certificate already exists for #{name}" if Puppet::SSL::Certificate.indirection.find(name) @@ -187,7 +172,7 @@ class Puppet::SSL::CertificateAuthority 20.times { pass += (rand(74) + 48).chr } begin - Puppet.settings.write(:capass) { |f| f.print pass } + Puppet.settings.setting(:capass).open('w') { |f| f.print pass } rescue Errno::EACCES => detail raise Puppet::Error, "Could not write CA password: #{detail}" end @@ -222,26 +207,27 @@ class Puppet::SSL::CertificateAuthority # Read the next serial from the serial file, and increment the # file so this one is considered used. def next_serial - serial = nil - - # This is slightly odd. If the file doesn't exist, our readwritelock creates - # it, but with a mode we can't actually read in some cases. So, use - # a default before the lock. - serial = 0x1 unless FileTest.exist?(Puppet[:serial]) + serial = 1 + Puppet.settings.setting(:serial).exclusive_open('a+') do |f| + f.rewind + serial = f.read.chomp.hex + if serial == 0 + serial = 1 + end - Puppet.settings.readwritelock(:serial) { |f| - serial ||= File.read(Puppet.settings[:serial]).chomp.hex if FileTest.exist?(Puppet[:serial]) + f.truncate(0) + f.rewind # We store the next valid serial, not the one we just used. f << "%04X" % (serial + 1) - } + end serial end # Does the password file exist? def password? - FileTest.exist? Puppet[:capass] + Puppet::FileSystem::File.exist? Puppet[:capass] end # Print a given host's certificate as text. @@ -323,8 +309,11 @@ class Puppet::SSL::CertificateAuthority def check_internal_signing_policies(hostname, csr, allow_dns_alt_names) # Reject unknown request extensions. - unknown_req = csr.request_extensions. - reject {|x| RequestExtensionWhitelist.include? x["oid"] } + unknown_req = csr.request_extensions.reject do |x| + RequestExtensionWhitelist.include? x["oid"] or + Puppet::SSL::Oids.subtree_of?('ppRegCertExt', x["oid"], true) or + Puppet::SSL::Oids.subtree_of?('ppPrivCertExt', x["oid"], true) + end if unknown_req and not unknown_req.empty? names = unknown_req.map {|x| x["oid"] }.sort.uniq.join(", ") @@ -393,7 +382,7 @@ class Puppet::SSL::CertificateAuthority # # @return [OpenSSL::X509::Store] def x509_store(options = {}) - if (options[:cache]) + if (options[:cache]) return @x509store unless @x509store.nil? @x509store = create_x509_store else @@ -407,11 +396,13 @@ class Puppet::SSL::CertificateAuthority # # @return [OpenSSL::X509::Store] def create_x509_store - store = OpenSSL::X509::Store.new - store.add_file Puppet[:cacert] - store.add_crl crl.content if self.crl + store = OpenSSL::X509::Store.new() + store.add_file(Puppet[:cacert]) + store.add_crl(crl.content) if self.crl store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT - store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK_ALL|OpenSSL::X509::V_FLAG_CRL_CHECK if Puppet.settings[:certificate_revocation] + if Puppet.settings[:certificate_revocation] + store.flags = OpenSSL::X509::V_FLAG_CRL_CHECK_ALL | OpenSSL::X509::V_FLAG_CRL_CHECK + end store end private :create_x509_store @@ -472,4 +463,42 @@ class Puppet::SSL::CertificateAuthority def waiting? Puppet::SSL::CertificateRequest.indirection.search("*").collect { |r| r.name } end + + # @api private + class AutosignAlways + def allowed?(csr) + true + end + end + + # @api private + class AutosignNever + def allowed?(csr) + false + end + end + + # @api private + class AutosignConfig + def initialize(config_file) + @config = config_file + end + + def allowed?(csr) + autosign_store.allowed?(csr.name, '127.1.1.1') + end + + private + + def autosign_store + auth = Puppet::Network::AuthStore.new + @config.each_line do |line| + next if line =~ /^\s*#/ + next if line =~ /^\s*$/ + auth.allow(line.chomp) + end + + auth + end + end end diff --git a/lib/puppet/ssl/certificate_authority/autosign_command.rb b/lib/puppet/ssl/certificate_authority/autosign_command.rb new file mode 100644 index 000000000..52c066555 --- /dev/null +++ b/lib/puppet/ssl/certificate_authority/autosign_command.rb @@ -0,0 +1,44 @@ +require 'puppet/ssl/certificate_authority' + +# This class wraps a given command and invokes it with a CSR name and body to +# determine if the given CSR should be autosigned +# +# @api private +class Puppet::SSL::CertificateAuthority::AutosignCommand + + class CheckFailure < Puppet::Error; end + + def initialize(path) + @path = path + end + + # Run the autosign command with the given CSR name as an argument and the + # CSR body on stdin. + # + # @param name [String] The CSR name to check for autosigning + # @return [true, false] If the CSR should be autosigned + def allowed?(csr) + name = csr.name + cmd = [@path, name] + + output = Puppet::FileSystem::Tempfile.open('puppet-csr') do |csr_file| + csr_file.write(csr.to_s) + csr_file.flush + + execute_options = {:stdinfile => csr_file.path, :combine => true, :failonfail => false} + Puppet::Util::Execution.execute(cmd, execute_options) + end + + output.chomp! + + Puppet.debug "Autosign command '#{@path}' exit status: #{output.exitstatus}" + Puppet.debug "Autosign command '#{@path}' output: #{output}" + + case output.exitstatus + when 0 + true + else + false + end + end +end diff --git a/lib/puppet/ssl/certificate_authority/interface.rb b/lib/puppet/ssl/certificate_authority/interface.rb index 4e7a14ac8..b68368b8d 100644 --- a/lib/puppet/ssl/certificate_authority/interface.rb +++ b/lib/puppet/ssl/certificate_authority/interface.rb @@ -4,7 +4,9 @@ module Puppet # This class is basically a hidden class that knows how to act on the # CA. Its job is to provide a CLI-like interface to the CA class. class Interface - INTERFACE_METHODS = [:destroy, :list, :revoke, :generate, :sign, :print, :verify, :fingerprint] + INTERFACE_METHODS = [:destroy, :list, :revoke, :generate, :sign, :print, :verify, :fingerprint, :reinventory] + + SUBJECTLESS_METHODS = [:list, :reinventory] class InterfaceError < ArgumentError; end @@ -12,14 +14,17 @@ module Puppet # Actually perform the work. def apply(ca) - unless subjects or method == :list + unless subjects || SUBJECTLESS_METHODS.include?(method) raise ArgumentError, "You must provide hosts or --all when using #{method}" end - return send(method, ca) if respond_to?(method) - - (subjects == :all ? ca.list : subjects).each do |host| - ca.send(method, host) + # if the interface implements the method, use it instead of the ca's method + if respond_to?(method) + send(method, ca) + else + (subjects == :all ? ca.list : subjects).each do |host| + ca.send(method, host) + end end end @@ -66,14 +71,11 @@ module Puppet end if verify_error - cert = Puppet::SSL::Certificate.indirection.find(host) - certs[:invalid][host] = [cert, verify_error] + certs[:invalid][host] = [ Puppet::SSL::Certificate.indirection.find(host), verify_error ] elsif signed.include?(host) - cert = Puppet::SSL::Certificate.indirection.find(host) - certs[:signed][host] = cert + certs[:signed][host] = Puppet::SSL::Certificate.indirection.find(host) else - req = Puppet::SSL::CertificateRequest.indirection.find(host) - certs[:request][host] = req + certs[:request][host] = Puppet::SSL::CertificateRequest.indirection.find(host) end end @@ -147,7 +149,7 @@ module Puppet end end - # Sign a given certificate. + # Signs given certificates or waiting of subjects == :all def sign(ca) list = subjects == :all ? ca.waiting? : subjects raise InterfaceError, "No waiting certificate requests to sign" if list.empty? @@ -156,15 +158,17 @@ module Puppet end end + def reinventory(ca) + ca.inventory.rebuild + end + # Set the list of hosts we're operating on. Also supports keywords. def subjects=(value) - unless value == :all or value == :signed or value.is_a?(Array) + unless value == :all || value == :signed || value.is_a?(Array) raise ArgumentError, "Subjects must be an array or :all; not #{value}" end - value = nil if value.is_a?(Array) and value.empty? - - @subjects = value + @subjects = (value == []) ? nil : value end end end diff --git a/lib/puppet/ssl/certificate_factory.rb b/lib/puppet/ssl/certificate_factory.rb index 1e628ed83..c6d01026c 100644 --- a/lib/puppet/ssl/certificate_factory.rb +++ b/lib/puppet/ssl/certificate_factory.rb @@ -66,17 +66,7 @@ module Puppet::SSL::CertificateFactory inject({}) {|ret, val| ret.merge(val) } cert.extensions = exts.map do |oid, val| - val, crit = *val - val = val.join(', ') unless val.is_a? String - - # Enforce the X509v3 rules about subjectAltName being critical: - # specifically, it SHOULD NOT be critical if we have a subject, which we - # always do. --daniel 2011-10-18 - crit = false if oid == "subjectAltName" - - # val can be either a string, or [string, critical], and this does the - # right thing regardless of what we get passed. - ef.create_ext(oid, val, crit) + generate_extension(ef, oid, *val) end end @@ -144,5 +134,41 @@ module Puppet::SSL::CertificateFactory "nsCertType" => "client,email", } end -end + # Generate an extension with the given OID, value, and critical state + # + # @param oid [String] The numeric value or short name of a given OID. X509v3 + # extensions must be passed by short name or long name, while custom + # extensions may be passed by short name, long name, oid numeric OID. + # @param ef [OpenSSL::X509::ExtensionFactory] The extension factory to use + # when generating the extension. + # @param val [String, Array<String>] The extension value. + # @param crit [true, false] Whether the given extension is critical, defaults + # to false. + # + # @return [OpenSSL::X509::Extension] + # + # @api private + def self.generate_extension(ef, oid, val, crit = false) + + val = val.join(', ') unless val.is_a? String + + # Enforce the X509v3 rules about subjectAltName being critical: + # specifically, it SHOULD NOT be critical if we have a subject, which we + # always do. --daniel 2011-10-18 + crit = false if oid == "subjectAltName" + + if Puppet::SSL::Oids.subtree_of?('id-ce', oid) or Puppet::SSL::Oids.subtree_of?('id-pkix', oid) + # Attempt to create a X509v3 certificate extension. Standard certificate + # extensions may need access to the associated subject certificate and + # issuing certificate, so must be created by the OpenSSL::X509::ExtensionFactory + # which provides that context. + ef.create_ext(oid, val, crit) + else + # This is not an X509v3 extension which means that the extension + # factory cannot generate it. We need to generate the extension + # manually. + OpenSSL::X509::Extension.new(oid, val, crit) + end + end +end diff --git a/lib/puppet/ssl/certificate_request.rb b/lib/puppet/ssl/certificate_request.rb index cc27eebbe..cd68eb34a 100644 --- a/lib/puppet/ssl/certificate_request.rb +++ b/lib/puppet/ssl/certificate_request.rb @@ -1,7 +1,30 @@ require 'puppet/ssl/base' require 'puppet/ssl/certificate_signer' -# Manage certificate requests. +# This class creates and manages X509 certificate signing requests. +# +# ## CSR attributes +# +# CSRs may contain a set of attributes that includes supplementary information +# about the CSR or information for the signed certificate. +# +# PKCS#9/RFC 2985 section 5.4 formally defines the "Challenge password", +# "Extension request", and "Extended-certificate attributes", but this +# implementation only handles the "Extension request" attribute. Other +# attributes may be defined on a CSR, but the RFC doesn't define behavior for +# any other attributes so we treat them as only informational. +# +# ## CSR Extension request attribute +# +# CSRs may contain an optional set of extension requests, which allow CSRs to +# include additional information that may be included in the signed +# certificate. Any additional information that should be copied from the CSR +# to the signed certificate MUST be included in this attribute. +# +# This behavior is dictated by PKCS#9/RFC 2985 section 5.4.2. +# +# @see http://tools.ietf.org/html/rfc2985 "RFC 2985 Section 5.4.2 Extension request" +# class Puppet::SSL::CertificateRequest < Puppet::SSL::Base wraps OpenSSL::X509::Request @@ -14,7 +37,7 @@ class Puppet::SSL::CertificateRequest < Puppet::SSL::Base # Try to autosign the CSR. if ca = Puppet::SSL::CertificateAuthority.instance - ca.autosign + ca.autosign(instance) end end end @@ -34,7 +57,22 @@ DOC @ef ||= OpenSSL::X509::ExtensionFactory.new end - # How to create a certificate request with our system defaults. + # Create a certificate request with our system settings. + # + # @param key [OpenSSL::X509::Key, Puppet::SSL::Key] The key pair associated + # with this CSR. + # @param opts [Hash] + # @options opts [String] :dns_alt_names A comma separated list of + # Subject Alternative Names to include in the CSR extension request. + # @options opts [Hash<String, String, Array<String>>] :csr_attributes A hash + # of OIDs and values that are either a string or array of strings. + # @options opts [Array<String, String>] :extension_requests A hash of + # certificate extensions to add to the CSR extReq attribute, excluding + # the Subject Alternative Names extension. + # + # @raise [Puppet::Error] If the generated CSR signature couldn't be verified + # + # @return [OpenSSL::X509::Request] The generated CSR def generate(key, options = {}) Puppet.info "Creating a new SSL certificate request for #{name}" @@ -51,16 +89,12 @@ DOC csr.subject = OpenSSL::X509::Name.new([["CN", common_name]]) csr.public_key = key.public_key - if options[:dns_alt_names] then - names = options[:dns_alt_names].split(/\s*,\s*/).map(&:strip) + [name] - names = names.sort.uniq.map {|name| "DNS:#{name}" }.join(", ") - names = extension_factory.create_extension("subjectAltName", names, false) - - extReq = OpenSSL::ASN1::Set([OpenSSL::ASN1::Sequence([names])]) + if options[:csr_attributes] + add_csr_attributes(csr, options[:csr_attributes]) + end - # We only support the standard request extensions. If you really need - # msExtReq support, let us know and we can restore them. --daniel 2011-10-10 - csr.add_attribute(OpenSSL::X509::Attribute.new("extReq", extReq)) + if (ext_req_attribute = extension_request_attribute(options)) + csr.add_attribute(ext_req_attribute) end signer = Puppet::SSL::CertificateSigner.new @@ -74,72 +108,192 @@ DOC end # Return the set of extensions requested on this CSR, in a form designed to - # be useful to Ruby: a hash. Which, not coincidentally, you can pass + # be useful to Ruby: an array of hashes. Which, not coincidentally, you can pass # successfully to the OpenSSL constructor later, if you want. + # + # @return [Array<Hash{String => String}>] An array of two or three element + # hashes, with key/value pairs for the extension's oid, its value, and + # optionally its critical state. def request_extensions raise Puppet::Error, "CSR needs content to extract fields" unless @content # Prefer the standard extReq, but accept the Microsoft specific version as # a fallback, if the standard version isn't found. - ext = @content.attributes.find {|x| x.oid == "extReq" } or - @content.attributes.find {|x| x.oid == "msExtReq" } - return [] unless ext - - # Assert the structure and extract the names into an array of arrays. - unless ext.value.is_a? OpenSSL::ASN1::Set - raise Puppet::Error, "In #{ext.oid}, expected Set but found #{ext.value.class}" - end - - unless ext.value.value.is_a? Array - raise Puppet::Error, "In #{ext.oid}, expected Set[Array] but found #{ext.value.value.class}" - end - - unless ext.value.value.length == 1 - raise Puppet::Error, "In #{ext.oid}, expected Set[Array[...]], but found #{ext.value.value.length} items in the array" - end + attribute = @content.attributes.find {|x| x.oid == "extReq" } + attribute ||= @content.attributes.find {|x| x.oid == "msExtReq" } + return [] unless attribute - san = ext.value.value.first - unless san.is_a? OpenSSL::ASN1::Sequence - raise Puppet::Error, "In #{ext.oid}, expected Set[Array[Sequence[...]]], but found #{san.class}" - end - san = san.value + extensions = unpack_extension_request(attribute) - # OK, now san should be the array of items, validate that... index = -1 - san.map do |name| + extensions.map do |ext_values| index += 1 - - unless name.is_a? OpenSSL::ASN1::Sequence - raise Puppet::Error, "In #{ext.oid}, expected request extension record #{index} to be a Sequence, but found #{name.class}" - end - name = name.value + context = "#{attribute.oid} extension index #{index}" # OK, turn that into an extension, to unpack the content. Lovely that # we have to swap the order of arguments to the underlying method, or # perhaps that the ASN.1 representation chose to pack them in a # strange order where the optional component comes *earlier* than the # fixed component in the sequence. - case name.length + case ext_values.length when 2 - ev = OpenSSL::X509::Extension.new(name[0].value, name[1].value) + ev = OpenSSL::X509::Extension.new(ext_values[0].value, ext_values[1].value) { "oid" => ev.oid, "value" => ev.value } when 3 - ev = OpenSSL::X509::Extension.new(name[0].value, name[2].value, name[1].value) + ev = OpenSSL::X509::Extension.new(ext_values[0].value, ext_values[2].value, ext_values[1].value) { "oid" => ev.oid, "value" => ev.value, "critical" => ev.critical? } else - raise Puppet::Error, "In #{ext.oid}, expected extension record #{index} to have two or three items, but found #{name.length}" + raise Puppet::Error, "In #{attribute.oid}, expected extension record #{index} to have two or three items, but found #{ext_values.length}" end - end.flatten + end end def subject_alt_names @subject_alt_names ||= request_extensions. - select {|x| x["oid"] = "subjectAltName" }. + select {|x| x["oid"] == "subjectAltName" }. map {|x| x["value"].split(/\s*,\s*/) }. flatten. sort. uniq end + + # Return all user specified attributes attached to this CSR as a hash. IF an + # OID has a single value it is returned as a string, otherwise all values are + # returned as an array. + # + # The format of CSR attributes is specified in PKCS#10/RFC 2986 + # + # @see http://tools.ietf.org/html/rfc2986 "RFC 2986 Certification Request Syntax Specification" + # + # @api public + # + # @return [Hash<String, String>] + def custom_attributes + x509_attributes = @content.attributes.reject do |attr| + PRIVATE_CSR_ATTRIBUTES.include? attr.oid + end + + x509_attributes.map do |attr| + {"oid" => attr.oid, "value" => attr.value.first.value} + end + end + + private + + # Exclude OIDs that may conflict with how Puppet creates CSRs. + # + # We only have nominal support for Microsoft extension requests, but since we + # ultimately respect that field when looking for extension requests in a CSR + # we need to prevent that field from being written to directly. + PRIVATE_CSR_ATTRIBUTES = [ + 'extReq', '1.2.840.113549.1.9.14', + 'msExtReq', '1.3.6.1.4.1.311.2.1.14', + ] + + def add_csr_attributes(csr, csr_attributes) + csr_attributes.each do |oid, value| + begin + if PRIVATE_CSR_ATTRIBUTES.include? oid + raise ArgumentError, "Cannot specify CSR attribute #{oid}: conflicts with internally used CSR attribute" + end + + encoded = OpenSSL::ASN1::PrintableString.new(value.to_s) + + attr_set = OpenSSL::ASN1::Set.new([encoded]) + csr.add_attribute(OpenSSL::X509::Attribute.new(oid, attr_set)) + Puppet.debug("Added csr attribute: #{oid} => #{attr_set.inspect}") + rescue OpenSSL::X509::AttributeError => e + raise Puppet::Error, "Cannot create CSR with attribute #{oid}: #{e.message}" + end + end + end + + private + + PRIVATE_EXTENSIONS = [ + 'subjectAltName', '2.5.29.17', + ] + + # @api private + def extension_request_attribute(options) + extensions = [] + + if options[:extension_requests] + options[:extension_requests].each_pair do |oid, value| + begin + if PRIVATE_EXTENSIONS.include? oid + raise Puppet::Error, "Cannot specify CSR extension request #{oid}: conflicts with internally used extension request" + end + + ext = OpenSSL::X509::Extension.new(oid, value.to_s, false) + extensions << ext + rescue OpenSSL::X509::ExtensionError => e + raise Puppet::Error, "Cannot create CSR with extension request #{oid}: #{e.message}" + end + end + end + + if options[:dns_alt_names] + names = options[:dns_alt_names].split(/\s*,\s*/).map(&:strip) + [name] + names = names.sort.uniq.map {|name| "DNS:#{name}" }.join(", ") + alt_names_ext = extension_factory.create_extension("subjectAltName", names, false) + + extensions << alt_names_ext + end + + unless extensions.empty? + seq = OpenSSL::ASN1::Sequence(extensions) + ext_req = OpenSSL::ASN1::Set([seq]) + OpenSSL::X509::Attribute.new("extReq", ext_req) + end + end + + # Unpack the extReq attribute into an array of Extensions. + # + # The extension request attribute is structured like + # `Set[Sequence[Extensions]]` where the outer Set only contains a single + # sequence. + # + # In addition the Ruby implementation of ASN1 requires that all ASN1 values + # contain a single value, so Sets and Sequence have to contain an array + # that in turn holds the elements. This is why we have to unpack an array + # every time we unpack a Set/Seq. + # + # @see http://tools.ietf.org/html/rfc2985#ref-10 5.4.2 CSR Extension Request structure + # @see http://tools.ietf.org/html/rfc5280 4.1 Certificate Extension structure + # + # @api private + # + # @param attribute [OpenSSL::X509::Attribute] The X509 extension request + # + # @return [Array<Array<Object>>] A array of arrays containing the extension + # OID the critical state if present, and the extension value. + def unpack_extension_request(attribute) + + unless attribute.value.is_a? OpenSSL::ASN1::Set + raise Puppet::Error, "In #{attribute.oid}, expected Set but found #{attribute.value.class}" + end + + unless attribute.value.value.is_a? Array + raise Puppet::Error, "In #{attribute.oid}, expected Set[Array] but found #{attribute.value.value.class}" + end + + unless attribute.value.value.size == 1 + raise Puppet::Error, "In #{attribute.oid}, expected Set[Array] with one value but found #{attribute.value.value.size} elements" + end + + unless attribute.value.value.first.is_a? OpenSSL::ASN1::Sequence + raise Puppet::Error, "In #{attribute.oid}, expected Set[Array[Sequence[...]]], but found #{extension.class}" + end + + unless attribute.value.value.first.value.is_a? Array + raise Puppet::Error, "In #{attribute.oid}, expected Set[Array[Sequence[Array[...]]]], but found #{extension.value.class}" + end + + extensions = attribute.value.value.first.value + + extensions.map(&:value) + end end diff --git a/lib/puppet/ssl/certificate_request_attributes.rb b/lib/puppet/ssl/certificate_request_attributes.rb new file mode 100644 index 000000000..e65b01443 --- /dev/null +++ b/lib/puppet/ssl/certificate_request_attributes.rb @@ -0,0 +1,37 @@ +require 'puppet/ssl' +require 'puppet/util/yaml' + +# This class transforms simple key/value pairs into the equivalent ASN1 +# structures. Values may be strings or arrays of strings. +# +# @api private +class Puppet::SSL::CertificateRequestAttributes + + attr_reader :path, :custom_attributes, :extension_requests + + def initialize(path) + @path = path + @custom_attributes = {} + @extension_requests = {} + end + + # Attempt to load a yaml file at the given @path. + # @return true if we are able to load the file, false otherwise + # @raise [Puppet::Error] if there are unexpected attribute keys + def load + Puppet.info("csr_attributes file loading from #{path}") + if Puppet::FileSystem::File.exist?(path) + hash = Puppet::Util::Yaml.load_file(path, {}) + if ! hash.is_a?(Hash) + raise Puppet::Error, "invalid CSR attributes, expected instance of Hash, received instance of #{hash.class}" + end + @custom_attributes = hash.delete('custom_attributes') || {} + @extension_requests = hash.delete('extension_requests') || {} + if not hash.keys.empty? + raise Puppet::Error, "unexpected attributes #{hash.keys.inspect} in #{@path.inspect}" + end + return true + end + return false + end +end diff --git a/lib/puppet/ssl/certificate_revocation_list.rb b/lib/puppet/ssl/certificate_revocation_list.rb index bb6595477..e19534764 100644 --- a/lib/puppet/ssl/certificate_revocation_list.rb +++ b/lib/puppet/ssl/certificate_revocation_list.rb @@ -49,7 +49,7 @@ DOC Puppet.notice "Revoked certificate with serial #{serial}" time = Time.now - add_certitificate_revocation_for(serial, reason, time) + add_certificate_revocation_for(serial, reason, time) update_to_next_crl_number update_valid_time_range_to_start_at(time) sign_with(cakey) @@ -69,7 +69,7 @@ private @content.extensions = [crl_number_of(0)] end - def add_certitificate_revocation_for(serial, reason, time) + def add_certificate_revocation_for(serial, reason, time) revoked = OpenSSL::X509::Revoked.new revoked.serial = serial revoked.time = time diff --git a/lib/puppet/ssl/host.rb b/lib/puppet/ssl/host.rb index e97ffe6c0..f30b4dee7 100644 --- a/lib/puppet/ssl/host.rb +++ b/lib/puppet/ssl/host.rb @@ -4,6 +4,7 @@ require 'puppet/ssl/key' require 'puppet/ssl/certificate' require 'puppet/ssl/certificate_request' require 'puppet/ssl/certificate_revocation_list' +require 'puppet/ssl/certificate_request_attributes' # The class that manages all aspects of our SSL certificates -- # private keys, public keys, requests, etc. @@ -173,6 +174,12 @@ DOC end end + csr_attributes = Puppet::SSL::CertificateRequestAttributes.new(Puppet[:csr_attributes]) + if csr_attributes.load + options[:csr_attributes] = csr_attributes.custom_attributes + options[:extension_requests] = csr_attributes.extension_requests + end + @certificate_request = CertificateRequest.new(name) @certificate_request.generate(key.content, options) begin @@ -264,14 +271,14 @@ ERROR_STRING @ssl_store end - def to_pson(*args) + def to_data_hash my_cert = Puppet::SSL::Certificate.indirection.find(name) - pson_hash = { :name => name } + result = { :name => name } my_state = state - pson_hash[:state] = my_state - pson_hash[:desired_state] = desired_state if desired_state + result[:state] = my_state + result[:desired_state] = desired_state if desired_state thing_to_use = (my_state == 'requested') ? certificate_request : my_cert @@ -280,7 +287,7 @@ ERROR_STRING # pson[:fingerprints][:default] # It appears that we have no internal consumers of this api # --jeffweiss 30 aug 2012 - pson_hash[:fingerprint] = thing_to_use.fingerprint + result[:fingerprint] = thing_to_use.fingerprint # The above fingerprint doesn't tell us what message digest algorithm was used # No problem, except that the default is changing between 2.7 and 3.0. Also, as @@ -289,15 +296,19 @@ ERROR_STRING # So, when we add the newer fingerprints, we're explicit about the hashing # algorithm used. # --jeffweiss 31 july 2012 - pson_hash[:fingerprints] = {} - pson_hash[:fingerprints][:default] = thing_to_use.fingerprint + result[:fingerprints] = {} + result[:fingerprints][:default] = thing_to_use.fingerprint suitable_message_digest_algorithms.each do |md| - pson_hash[:fingerprints][md] = thing_to_use.fingerprint md + result[:fingerprints][md] = thing_to_use.fingerprint md end - pson_hash[:dns_alt_names] = thing_to_use.subject_alt_names + result[:dns_alt_names] = thing_to_use.subject_alt_names - pson_hash.to_pson(*args) + result + end + + def to_pson(*args) + to_data_hash.to_pson(*args) end # eventually we'll probably want to move this somewhere else or make it diff --git a/lib/puppet/ssl/inventory.rb b/lib/puppet/ssl/inventory.rb index c210fdc35..3aae02a65 100644 --- a/lib/puppet/ssl/inventory.rb +++ b/lib/puppet/ssl/inventory.rb @@ -8,11 +8,7 @@ class Puppet::SSL::Inventory # Add a certificate to our inventory. def add(cert) cert = cert.content if cert.is_a?(Puppet::SSL::Certificate) - - # Create our file, if one does not already exist. - rebuild unless FileTest.exist?(@path) - - Puppet.settings.write(:cert_inventory, "a") do |f| + Puppet.settings.setting(:cert_inventory).open("a") do |f| f.print format(cert) end end @@ -32,16 +28,16 @@ class Puppet::SSL::Inventory def rebuild Puppet.notice "Rebuilding inventory file" - Puppet.settings.write(:cert_inventory) do |f| - f.print "# Inventory of signed certificates\n# SERIAL NOT_BEFORE NOT_AFTER SUBJECT\n" + Puppet.settings.setting(:cert_inventory).open('w') do |f| + Puppet::SSL::Certificate.indirection.search("*").each do |cert| + f.print format(cert.content) + end end - - Puppet::SSL::Certificate.indirection.search("*").each { |cert| add(cert) } end # Find the serial number for a given certificate. def serial(name) - return nil unless FileTest.exist?(@path) + return nil unless Puppet::FileSystem::File.exist?(@path) File.readlines(@path).each do |line| next unless line =~ /^(\S+).+\/CN=#{name}$/ diff --git a/lib/puppet/ssl/key.rb b/lib/puppet/ssl/key.rb index 569fb706d..b64fde544 100644 --- a/lib/puppet/ssl/key.rb +++ b/lib/puppet/ssl/key.rb @@ -36,7 +36,7 @@ DOC end def password - return nil unless password_file and FileTest.exist?(password_file) + return nil unless password_file and Puppet::FileSystem::File.exist?(password_file) ::File.read(password_file) end diff --git a/lib/puppet/ssl/oids.rb b/lib/puppet/ssl/oids.rb new file mode 100644 index 000000000..6d0a8d0d9 --- /dev/null +++ b/lib/puppet/ssl/oids.rb @@ -0,0 +1,78 @@ +require 'puppet/ssl' + +# This module defines OIDs for use within Puppet. +# +# == ASN.1 Definition +# +# The following is the formal definition of OIDs specified in this file. +# +# puppetCertExtensions OBJECT IDENTIFIER ::= {iso(1) identified-organization(3) +# dod(6) internet(1) private(4) enterprise(1) 34380 1} +# +# -- the tree under registeredExtensions 'belongs' to puppetlabs +# -- privateExtensions can be extended by enterprises to suit their own needs +# registeredExtensions OBJECT IDENTIFIER ::= { puppetCertExtensions 1 } +# privateExtensions OBJECT IDENTIFIER ::= { puppetCertExtensions 2 } +# +# -- subtree of common registered extensions +# -- The short names for these OIDs are intentionally lowercased and formatted +# -- since they may be exposed inside the Puppet DSL as variables. +# pp_uuid OBJECT IDENTIFIER ::= { registeredExtensions 1 } +# pp_instance_id OBJECT IDENTIFIER ::= { registeredExtensions 2 } +# pp_image_name OBJECT IDENTIFIER ::= { registeredExtensions 3 } +# pp_preshared_key OBJECT IDENTIFIER ::= { registeredExtensions 4 } +# +# @api private +module Puppet::SSL::Oids + + PUPPET_OIDS = [ + ["1.3.6.1.4.1.34380", 'puppetlabs', 'Puppet Labs'], + ["1.3.6.1.4.1.34380.1", 'ppCertExt', 'Puppet Certificate Extension'], + + ["1.3.6.1.4.1.34380.1.1", 'ppRegCertExt', 'Puppet Registered Certificate Extension'], + + ["1.3.6.1.4.1.34380.1.1.1", 'pp_uuid', 'Puppet Node UUID'], + ["1.3.6.1.4.1.34380.1.1.2", 'pp_instance_id', 'Puppet Node Instance ID'], + ["1.3.6.1.4.1.34380.1.1.3", 'pp_image_name', 'Puppet Node Image Name'], + ["1.3.6.1.4.1.34380.1.1.4", 'pp_preshared_key', 'Puppet Node Preshared Key'], + + ["1.3.6.1.4.1.34380.1.2", 'ppPrivCertExt', 'Puppet Private Certificate Extension'], + ] + + PUPPET_OIDS.each do |oid_defn| + OpenSSL::ASN1::ObjectId.register(*oid_defn) + end + + # Determine if the first OID contains the second OID + # + # @param first [String] The containing OID, in dotted form or as the short name + # @param second [String] The contained OID, in dotted form or as the short name + # @param exclusive [true, false] If an OID should not be considered as a subtree of itself + # + # @example Comparing two dotted OIDs + # Puppet::SSL::Oids.subtree_of?('1.3.6.1', '1.3.6.1.4.1') #=> true + # Puppet::SSL::Oids.subtree_of?('1.3.6.1', '1.3.6') #=> false + # + # @example Comparing an OID short name with a dotted OID + # Puppet::SSL::Oids.subtree_of?('IANA', '1.3.6.1.4.1') #=> true + # Puppet::SSL::Oids.subtree_of?('1.3.6.1', 'enterprises') #=> true + # + # @example Comparing an OID against itself + # Puppet::SSL::Oids.subtree_of?('IANA', 'IANA') #=> true + # Puppet::SSL::Oids.subtree_of?('IANA', 'IANA', true) #=> false + # + # @return [true, false] + def self.subtree_of?(first, second, exclusive = false) + first_oid = OpenSSL::ASN1::ObjectId.new(first).oid + second_oid = OpenSSL::ASN1::ObjectId.new(second).oid + + + if exclusive and first_oid == second_oid + false + else + second_oid.index(first_oid) == 0 + end + rescue OpenSSL::ASN1::ASN1Error + false + end +end diff --git a/lib/puppet/ssl/validator.rb b/lib/puppet/ssl/validator.rb index 18255fb3c..754a74233 100644 --- a/lib/puppet/ssl/validator.rb +++ b/lib/puppet/ssl/validator.rb @@ -1,116 +1,60 @@ -require 'puppet/ssl' require 'openssl' -module Puppet -module SSL -class Validator - attr_reader :peer_certs - attr_reader :verify_errors - attr_reader :ssl_configuration - ## - # @param [Hash] opts the options to initialze the instance with. - # - # @option opts [Puppet::SSL::Configuration] :ssl_configuration to use for - # authorizing the peer certificate chain. - def initialize(opts = {}) - reset! - @ssl_configuration = opts[:ssl_configuration] or raise ArgumentError, ":ssl_configuration is required" - end +# API for certificate verification +# +# @api public +class Puppet::SSL::Validator - ## - # reset to the initial state. - def reset! - @peer_certs = [] - @verify_errors = [] + # Factory method for creating an instance of a null/no validator. + # This method does not have to be implemented by concrete implementations of this API. + # + # @return [Puppet::SSL::Validator] produces a validator that performs no validation + # + # @api public + # + def self.no_validator() + @@no_validator_cache ||= Puppet::SSL::Validator::NoValidator.new() end - ## - # call performs verification of the SSL connection and collection of the - # certificates for use in constructing the error message if the verification - # failed. This callback will be executed once for each certificate in a - # chain being verified. + # Factory method for creating an instance of the default Puppet validator. + # This method does not have to be implemented by concrete implementations of this API. # - # From the [OpenSSL - # documentation](http://www.openssl.org/docs/ssl/SSL_CTX_set_verify.html): - # The `verify_callback` function is used to control the behaviour when the - # SSL_VERIFY_PEER flag is set. It must be supplied by the application and - # receives two arguments: preverify_ok indicates, whether the verification of - # the certificate in question was passed (preverify_ok=1) or not - # (preverify_ok=0). x509_ctx is a pointer to the complete context used for - # the certificate chain verification. + # @return [Puppet::SSL::Validator] produces a validator that performs no validation # - # See {Puppet::Network::HTTP::Connection} for more information and where this - # class is intended to be used. + # @api public # - # @param [Boolean] preverify_ok indicates whether the verification of the - # certificate in question was passed (preverify_ok=true) - # @param [OpenSSL::SSL::SSLContext] ssl_context holds the SSLContext for the - # chain being verified. - # - # @return [Boolean] false if the peer is invalid, true otherwise. - def call(preverify_ok, ssl_context) - # We must make a copy since the scope of the ssl_context will be lost - # across invocations of this method. - current_cert = ssl_context.current_cert - @peer_certs << Puppet::SSL::Certificate.from_instance(current_cert) - - if preverify_ok - # If we've copied all of the certs in the chain out of the SSL library - if @peer_certs.length == ssl_context.chain.length - # (#20027) The peer cert must be issued by a specific authority - preverify_ok = valid_peer? - end - else - if ssl_context.error_string - @verify_errors << "#{ssl_context.error_string} for #{current_cert.subject}" - end - end - preverify_ok - rescue => ex - @verify_errors << ex.message - false + def self.default_validator() + Puppet::SSL::Validator::DefaultValidator.new() end - ## - # Register the instance's call method with the connection. + # Array of peer certificates + # @return [Array<Puppet::SSL::Certificate>] peer certificates # - # @param [Net::HTTP] connection The connection to velidate + # @api public # - # @return [void] - def register_verify_callback(connection) - connection.verify_callback = self + def peer_certs + raise NotImplementedError, "Concrete class should have implemented this method" end - ## - # Validate the peer certificates against the authorized certificates. - def valid_peer? - descending_cert_chain = @peer_certs.reverse.map {|c| c.content } - authz_ca_certs = ssl_configuration.ca_auth_certificates - - if not has_authz_peer_cert(descending_cert_chain, authz_ca_certs) - msg = "The server presented a SSL certificate chain which does not include a " << - "CA listed in the ssl_client_ca_auth file. " - msg << "Authorized Issuers: #{authz_ca_certs.collect {|c| c.subject}.join(', ')} " << - "Peer Chain: #{descending_cert_chain.collect {|c| c.subject}.join(' => ')}" - @verify_errors << msg - false - else - true - end + # Contains the result of validation + # @return [Array<String>, nil] nil, empty Array, or Array with messages + # + # @api public + # + def verify_errors + raise NotImplementedError, "Concrete class should have implemented this method" end - ## - # checks if the set of peer_certs contains at least one certificate issued - # by a certificate listed in authz_certs + # Registers the connection to validate. + # + # @param [Net::HTTP] connection The connection to validate + # + # @return [void] # - # @return [Boolean] - def has_authz_peer_cert(peer_certs, authz_certs) - peer_certs.any? do |peer_cert| - authz_certs.any? do |authz_cert| - peer_cert.verify(authz_cert.public_key) - end - end + # @api public + # + def setup_connection(connection) + raise NotImplementedError, "Concrete class should have implemented this method" end end -end -end + diff --git a/lib/puppet/ssl/validator/default_validator.rb b/lib/puppet/ssl/validator/default_validator.rb new file mode 100644 index 000000000..1f238f4c4 --- /dev/null +++ b/lib/puppet/ssl/validator/default_validator.rb @@ -0,0 +1,153 @@ +require 'openssl' + +# Perform peer certificate verification against the known CA. +# If there is no CA information known, then no verification is performed +# +# @api private +# +class Puppet::SSL::Validator::DefaultValidator #< class Puppet::SSL::Validator + attr_reader :peer_certs + attr_reader :verify_errors + attr_reader :ssl_configuration + + # Creates a new DefaultValidator, optionally with an SSL Configuration and SSL Host. + # + # @param [Puppet::SSL::Configuration] (a default configuration) ssl_configuration the SSL configuration to use + # @param [Puppet::SSL::Host] (Puppet::SSL::Host.localhost) the SSL host to use + # + # @api private + # + def initialize( + ssl_configuration = Puppet::SSL::Configuration.new( + Puppet[:localcacert], { + :ca_chain_file => Puppet[:ssl_client_ca_chain], + :ca_auth_file => Puppet[:ssl_client_ca_auth] + }), + ssl_host = Puppet::SSL::Host.localhost) + + reset! + @ssl_configuration = ssl_configuration + @ssl_host = ssl_host + end + + + # Resets this validator to its initial validation state. The ssl configuration is not changed. + # + # @api private + # + def reset! + @peer_certs = [] + @verify_errors = [] + end + + # Performs verification of the SSL connection and collection of the + # certificates for use in constructing the error message if the verification + # failed. This callback will be executed once for each certificate in a + # chain being verified. + # + # From the [OpenSSL + # documentation](http://www.openssl.org/docs/ssl/SSL_CTX_set_verify.html): + # The `verify_callback` function is used to control the behaviour when the + # SSL_VERIFY_PEER flag is set. It must be supplied by the application and + # receives two arguments: preverify_ok indicates, whether the verification of + # the certificate in question was passed (preverify_ok=1) or not + # (preverify_ok=0). x509_ctx is a pointer to the complete context used for + # the certificate chain verification. + # + # See {Puppet::Network::HTTP::Connection} for more information and where this + # class is intended to be used. + # + # @param [Boolean] preverify_ok indicates whether the verification of the + # certificate in question was passed (preverify_ok=true) + # @param [OpenSSL::SSL::SSLContext] ssl_context holds the SSLContext for the + # chain being verified. + # + # @return [Boolean] false if the peer is invalid, true otherwise. + # + # @api private + # + def call(preverify_ok, ssl_context) + # We must make a copy since the scope of the ssl_context will be lost + # across invocations of this method. + current_cert = ssl_context.current_cert + @peer_certs << Puppet::SSL::Certificate.from_instance(current_cert) + + if preverify_ok + # If we've copied all of the certs in the chain out of the SSL library + if @peer_certs.length == ssl_context.chain.length + # (#20027) The peer cert must be issued by a specific authority + preverify_ok = valid_peer? + end + else + if ssl_context.error_string + @verify_errors << "#{ssl_context.error_string} for #{current_cert.subject}" + end + end + preverify_ok + rescue => ex + @verify_errors << ex.message + false + end + + # Registers the instance's call method with the connection. + # + # @param [Net::HTTP] connection The connection to validate + # + # @return [void] + # + # @api private + # + def setup_connection(connection) + if ssl_certificates_are_present? + connection.cert_store = @ssl_host.ssl_store + connection.ca_file = @ssl_configuration.ca_auth_file + connection.cert = @ssl_host.certificate.content + connection.key = @ssl_host.key.content + connection.verify_mode = OpenSSL::SSL::VERIFY_PEER + connection.verify_callback = self + else + connection.verify_mode = OpenSSL::SSL::VERIFY_NONE + end + end + + # Validates the peer certificates against the authorized certificates. + # + # @api private + # + def valid_peer? + descending_cert_chain = @peer_certs.reverse.map {|c| c.content } + authz_ca_certs = ssl_configuration.ca_auth_certificates + + if not has_authz_peer_cert(descending_cert_chain, authz_ca_certs) + msg = "The server presented a SSL certificate chain which does not include a " << + "CA listed in the ssl_client_ca_auth file. " + msg << "Authorized Issuers: #{authz_ca_certs.collect {|c| c.subject}.join(', ')} " << + "Peer Chain: #{descending_cert_chain.collect {|c| c.subject}.join(' => ')}" + @verify_errors << msg + false + else + true + end + end + + # Checks if the set of peer_certs contains at least one certificate issued + # by a certificate listed in authz_certs + # + # @return [Boolean] + # + # @api private + # + def has_authz_peer_cert(peer_certs, authz_certs) + peer_certs.any? do |peer_cert| + authz_certs.any? do |authz_cert| + peer_cert.verify(authz_cert.public_key) + end + end + end + + # @api private + # + def ssl_certificates_are_present? + Puppet::FileSystem::File.exist?(Puppet[:hostcert]) && Puppet::FileSystem::File.exist?(@ssl_configuration.ca_auth_file) + end +end diff --git a/lib/puppet/ssl/validator/no_validator.rb b/lib/puppet/ssl/validator/no_validator.rb new file mode 100644 index 000000000..1141b6952 --- /dev/null +++ b/lib/puppet/ssl/validator/no_validator.rb @@ -0,0 +1,17 @@ +# Performs no SSL verification +# @api private +# +class Puppet::SSL::Validator::NoValidator < Puppet::SSL::Validator + + def setup_connection(connection) + connection.verify_mode = OpenSSL::SSL::VERIFY_NONE + end + + def peer_certs + [] + end + + def verify_errors + [] + end +end diff --git a/lib/puppet/status.rb b/lib/puppet/status.rb index 9ad55668b..0b26ae22e 100644 --- a/lib/puppet/status.rb +++ b/lib/puppet/status.rb @@ -10,6 +10,10 @@ class Puppet::Status @status = status || {"is_alive" => true} end + def to_data_hash + @status + end + def to_pson(*args) @status.to_pson end diff --git a/lib/puppet/test/test_helper.rb b/lib/puppet/test/test_helper.rb index c09a1ade2..f80e699e7 100644 --- a/lib/puppet/test/test_helper.rb +++ b/lib/puppet/test/test_helper.rb @@ -1,3 +1,5 @@ +require 'puppet/indirector/data_binding/hiera' + module Puppet::Test # This class is intended to provide an API to be used by external projects # when they are running tests that depend on puppet core. This should @@ -84,8 +86,11 @@ module Puppet::Test Puppet::Node::Environment.clear Puppet::Parser::Functions.reset Puppet::Application.clear! + Puppet::Util::Profiler.clear Puppet.clear_deprecation_warnings + + Puppet::DataBinding::Hiera.instance_variable_set("@hiera", nil) end # Call this method once per test, after execution of each individual test. diff --git a/lib/puppet/transaction.rb b/lib/puppet/transaction.rb index a37ec59ad..ab74ed385 100644 --- a/lib/puppet/transaction.rb +++ b/lib/puppet/transaction.rb @@ -2,6 +2,7 @@ require 'puppet' require 'puppet/util/tagging' require 'puppet/application' require 'digest/sha1' +require 'set' # the class that actually walks our resource/property tree, collects the changes, # and performs them @@ -58,7 +59,11 @@ class Puppet::Transaction continue_while = lambda { !stop_processing? } + post_evalable_providers = Set.new pre_process = lambda do |resource| + prov_class = resource.provider.class + post_evalable_providers << prov_class if prov_class.respond_to?(:post_resource_eval) + prefetch_if_necessary(resource) # If we generated resources, we don't know what they are now @@ -90,6 +95,14 @@ class Puppet::Transaction providerless_types.uniq.each do |type| Puppet.err "Could not find a suitable provider for #{type}" end + + post_evalable_providers.each do |provider| + begin + provider.post_resource_eval + rescue => detail + Puppet.log_exception(detail, "post_resource_eval failed for provider #{provider}") + end + end end relationship_graph.traverse(:while => continue_while, diff --git a/lib/puppet/transaction/event.rb b/lib/puppet/transaction/event.rb index c832665c6..ca4255937 100644 --- a/lib/puppet/transaction/event.rb +++ b/lib/puppet/transaction/event.rb @@ -2,17 +2,18 @@ require 'puppet/transaction' require 'puppet/util/tagging' require 'puppet/util/logging' require 'puppet/util/methodhelper' +require 'puppet/network/format_support' # A simple struct for storing what happens on the system. class Puppet::Transaction::Event include Puppet::Util::MethodHelper include Puppet::Util::Tagging include Puppet::Util::Logging + include Puppet::Network::FormatSupport ATTRIBUTES = [:name, :resource, :property, :previous_value, :desired_value, :historical_value, :status, :message, :file, :line, :source_description, :audited, :invalidate_refreshes] YAML_ATTRIBUTES = %w{@audited @property @previous_value @desired_value @historical_value @message @name @status @time}.map(&:to_sym) attr_accessor *ATTRIBUTES - attr_writer :tags attr_accessor :time attr_reader :default_log_level @@ -44,7 +45,7 @@ class Puppet::Transaction::Event @time = Time.parse(@time) if @time.is_a? String end - def to_pson + def to_data_hash { 'audited' => @audited, 'property' => @property, @@ -55,7 +56,11 @@ class Puppet::Transaction::Event 'name' => @name, 'status' => @status, 'time' => @time.iso8601(9), - }.to_pson + } + end + + def to_pson(*args) + to_data_hash.to_pson(*args) end def property=(prop) diff --git a/lib/puppet/transaction/report.rb b/lib/puppet/transaction/report.rb index 92e685f42..0469965cd 100644 --- a/lib/puppet/transaction/report.rb +++ b/lib/puppet/transaction/report.rb @@ -216,7 +216,7 @@ class Puppet::Transaction::Report end end - def to_pson + def to_data_hash { 'host' => @host, 'time' => @time.iso8601(9), @@ -231,7 +231,11 @@ class Puppet::Transaction::Report 'logs' => @logs, 'metrics' => @metrics, 'resource_statuses' => @resource_statuses, - }.to_pson + } + end + + def to_pson + to_data_hash.to_pson end # @return [String] the host name diff --git a/lib/puppet/transaction/resource_harness.rb b/lib/puppet/transaction/resource_harness.rb index 99ac751b5..3efcabaf6 100644 --- a/lib/puppet/transaction/resource_harness.rb +++ b/lib/puppet/transaction/resource_harness.rb @@ -6,16 +6,50 @@ class Puppet::Transaction::ResourceHarness attr_reader :transaction - def allow_changes?(resource) - if resource.purging? and resource.deleting? and deps = relationship_graph.dependents(resource) \ - and ! deps.empty? and deps.detect { |d| ! d.deleting? } - deplabel = deps.collect { |r| r.ref }.join(",") - plurality = deps.length > 1 ? "":"s" - resource.warning "#{deplabel} still depend#{plurality} on me -- not purging" - false - else - true + def initialize(transaction) + @transaction = transaction + end + + def evaluate(resource) + status = Puppet::Resource::Status.new(resource) + + begin + context = ResourceApplicationContext.from_resource(resource, status) + perform_changes(resource, context) + + if status.changed? && ! resource.noop? + cache(resource, :synced, Time.now) + resource.flush if resource.respond_to?(:flush) + end + rescue => detail + status.failed_because(detail) + ensure + status.evaluation_time = Time.now - status.time + end + + status + end + + def scheduled?(resource) + return true if Puppet[:ignoreschedules] + return true unless schedule = schedule(resource) + + # We use 'checked' here instead of 'synced' because otherwise we'll + # end up checking most resources most times, because they will generally + # have been synced a long time ago (e.g., a file only gets updated + # once a month on the server and its schedule is daily; the last sync time + # will have been a month ago, so we'd end up checking every run). + schedule.match?(cached(resource, :checked).to_i) + end + + def schedule(resource) + unless resource.catalog + resource.warning "Cannot schedule without a schedule-containing catalog" + return nil end + + return nil unless name = resource[:schedule] + resource.catalog.resource(:schedule, name) || resource.fail("Could not find schedule #{name}") end # Used mostly for scheduling and auditing at this point. @@ -28,153 +62,177 @@ class Puppet::Transaction::ResourceHarness Puppet::Util::Storage.cache(resource)[name] = value end - def perform_changes(resource) - current_values = resource.retrieve_resource.to_hash + private + def perform_changes(resource, context) cache(resource, :checked, Time.now) return [] if ! allow_changes?(resource) - historical_values = Puppet::Util::Storage.cache(resource).dup - desired_values = {} - resource.properties.each do |property| - desired_values[property.name] = property.should - end - audited_params = (resource[:audit] || []).map { |p| p.to_sym } - synced_params = [] - # Record the current state in state.yml. - audited_params.each do |param| - cache(resource, param, current_values[param]) + context.audited_params.each do |param| + cache(resource, param, context.current_values[param]) end - # Update the machine state & create logs/events - events = [] - ensure_param = resource.parameter(:ensure) - if desired_values[:ensure] && !ensure_param.safe_insync?(current_values[:ensure]) - events << apply_parameter(ensure_param, current_values[:ensure], audited_params.include?(:ensure), historical_values[:ensure]) - synced_params << :ensure - elsif current_values[:ensure] != :absent - work_order = resource.properties # Note: only the resource knows what order to apply changes in - work_order.each do |param| - if desired_values[param.name] && !param.safe_insync?(current_values[param.name]) - events << apply_parameter(param, current_values[param.name], audited_params.include?(param.name), historical_values[param.name]) - synced_params << param.name + managed_via_ensure = manage_via_ensure_if_possible(resource, context) + + if !managed_via_ensure + if context.resource_present? + resource.properties.each do |param| + sync_if_needed(param, context) end + else + resource.debug("Nothing to manage: no ensure and the resource doesn't exist") end end - # Add more events to capture audit results - audited_params.each do |param_name| - if historical_values.include?(param_name) - if historical_values[param_name] != current_values[param_name] && !synced_params.include?(param_name) - event = create_change_event(resource.parameter(param_name), current_values[param_name], true, historical_values[param_name]) - event.send_log - events << event + capture_audit_events(resource, context) + end + + def allow_changes?(resource) + if resource.purging? and resource.deleting? and deps = relationship_graph.dependents(resource) \ + and ! deps.empty? and deps.detect { |d| ! d.deleting? } + deplabel = deps.collect { |r| r.ref }.join(",") + plurality = deps.length > 1 ? "":"s" + resource.warning "#{deplabel} still depend#{plurality} on me -- not purging" + false + else + true + end + end + + def manage_via_ensure_if_possible(resource, context) + ensure_param = resource.parameter(:ensure) + if ensure_param && ensure_param.should + sync_if_needed(ensure_param, context) + else + false + end + end + + def sync_if_needed(param, context) + historical_value = context.historical_values[param.name] + current_value = context.current_values[param.name] + do_audit = context.audited_params.include?(param.name) + + begin + if param.should && !param.safe_insync?(current_value) + event = create_change_event(param, current_value, historical_value) + if do_audit + event = audit_event(event, param) + end + + brief_audit_message = audit_message(param, do_audit, historical_value, current_value) + + if param.noop + noop(event, param, current_value, brief_audit_message) + else + sync(event, param, current_value, brief_audit_message) end + + true else - resource.property(param_name).notice "audit change: newly-recorded value #{current_values[param_name]}" + false end - end + rescue => detail + # Execution will continue on StandardErrors, just store the event + Puppet.log_exception(detail) - events + event = create_change_event(param, current_value, historical_value) + event.status = "failure" + event.message = "change from #{param.is_to_s(current_value)} to #{param.should_to_s(param.should)} failed: #{detail}" + false + rescue Exception => detail + # Execution will halt on Exceptions, they get raised to the application + event = create_change_event(param, current_value, historical_value) + event.status = "failure" + event.message = "change from #{param.is_to_s(current_value)} to #{param.should_to_s(param.should)} failed: #{detail}" + raise + ensure + if event + context.record(event) + event.send_log + context.synced_params << param.name + end + end end - def create_change_event(property, current_value, do_audit, historical_value) + def create_change_event(property, current_value, historical_value) event = property.event event.previous_value = current_value event.desired_value = property.should event.historical_value = historical_value - if do_audit - event.audited = true - event.status = "audit" - if historical_value != current_value - event.message = "audit change: previously recorded value #{property.is_to_s(historical_value)} has been changed to #{property.is_to_s(current_value)}" - end + event + end + + def audit_event(event, property) + event.audited = true + event.status = "audit" + if event.historical_value != event.previous_value + event.message = "audit change: previously recorded value #{property.is_to_s(event.historical_value)} has been changed to #{property.is_to_s(event.previous_value)}" end event end - def apply_parameter(property, current_value, do_audit, historical_value) - event = create_change_event(property, current_value, do_audit, historical_value) - + def audit_message(param, do_audit, historical_value, current_value) if do_audit && historical_value && historical_value != current_value - brief_audit_message = " (previously recorded value was #{property.is_to_s(historical_value)})" + " (previously recorded value was #{param.is_to_s(historical_value)})" else - brief_audit_message = "" + "" end - - if property.noop - event.message = "current_value #{property.is_to_s(current_value)}, should be #{property.should_to_s(property.should)} (noop)#{brief_audit_message}" - event.status = "noop" - else - property.sync - event.message = [ property.change_to_s(current_value, property.should), brief_audit_message ].join - event.status = "success" - end - event - rescue => detail - # Execution will continue on StandardErrors, just store the event - Puppet.log_exception(detail) - event.status = "failure" - - event.message = "change from #{property.is_to_s(current_value)} to #{property.should_to_s(property.should)} failed: #{detail}" - event - rescue Exception => detail - # Execution will halt on Exceptions, they get raised to the application - event.status = "failure" - event.message = "change from #{property.is_to_s(current_value)} to #{property.should_to_s(property.should)} failed: #{detail}" - raise - ensure - event.send_log end - def evaluate(resource) - status = Puppet::Resource::Status.new(resource) + def noop(event, param, current_value, audit_message) + event.message = "current_value #{param.is_to_s(current_value)}, should be #{param.should_to_s(param.should)} (noop)#{audit_message}" + event.status = "noop" + end - begin - perform_changes(resource).each do |event| - status << event - end + def sync(event, param, current_value, audit_message) + param.sync + event.message = "#{param.change_to_s(current_value, param.should)}#{audit_message}" + event.status = "success" + end - if status.changed? && ! resource.noop? - cache(resource, :synced, Time.now) - resource.flush if resource.respond_to?(:flush) + def capture_audit_events(resource, context) + context.audited_params.each do |param_name| + if context.historical_values.include?(param_name) + if context.historical_values[param_name] != context.current_values[param_name] && !context.synced_params.include?(param_name) + parameter = resource.parameter(param_name) + event = audit_event(create_change_event(parameter, + context.current_values[param_name], + context.historical_values[param_name]), + parameter) + event.send_log + context.record(event) + end + else + resource.property(param_name).notice "audit change: newly-recorded value #{context.current_values[param_name]}" end - rescue => detail - status.failed_because(detail) - ensure - status.evaluation_time = Time.now - status.time end - - status end - def initialize(transaction) - @transaction = transaction - end - - def scheduled?(resource) - return true if Puppet[:ignoreschedules] - return true unless schedule = schedule(resource) - - # We use 'checked' here instead of 'synced' because otherwise we'll - # end up checking most resources most times, because they will generally - # have been synced a long time ago (e.g., a file only gets updated - # once a month on the server and its schedule is daily; the last sync time - # will have been a month ago, so we'd end up checking every run). - schedule.match?(cached(resource, :checked).to_i) - end + # @api private + ResourceApplicationContext = Struct.new(:current_values, + :historical_values, + :audited_params, + :synced_params, + :status) do + def self.from_resource(resource, status) + ResourceApplicationContext.new(resource.retrieve_resource.to_hash, + Puppet::Util::Storage.cache(resource).dup, + (resource[:audit] || []).map { |p| p.to_sym }, + [], + status) + end - def schedule(resource) - unless resource.catalog - resource.warning "Cannot schedule without a schedule-containing catalog" - return nil + def resource_present? + current_values[:ensure] != :absent end - return nil unless name = resource[:schedule] - resource.catalog.resource(:schedule, name) || resource.fail("Could not find schedule #{name}") + def record(event) + status << event + end end end diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb index 55b4a5fad..941696d28 100644 --- a/lib/puppet/type.rb +++ b/lib/puppet/type.rb @@ -692,6 +692,20 @@ class Type } end + # Return the parameters, metaparams, and properties that have a value or were set by a default. Properties are + # included since they are a subclass of parameter. + # @return [Array<Puppet::Parameter>] Array of parameter objects ( or subclass thereof ) + def parameters_with_value + self.class.allattrs.collect { |attr| parameter(attr) }.compact + end + + # Iterates over all parameters with value currently set. + # @yieldparam parameter [Puppet::Parameter] or a subclass thereof + # @return [void] + def eachparameter + parameters_with_value.each { |parameter| yield parameter } + end + # Creates a transaction event. # Called by Transaction or by a property. # Merges the given options with the options `:resource`, `:file`, `:line`, and `:tags`, initialized from @@ -2278,6 +2292,13 @@ class Type # @return [Array<Puppet::Parameter>] the validated list/set of attributes # def finish + # Call post_compile hook on every parameter that implements it. This includes all subclasses + # of parameter including, but not limited to, regular parameters, metaparameters, relationship + # parameters, and properties. + eachparameter do |parameter| + parameter.post_compile if parameter.respond_to? :post_compile + end + # Make sure all of our relationships are valid. Again, must be done # when the entire catalog is instantiated. self.class.relationship_params.collect do |klass| @@ -2297,25 +2318,21 @@ class Type self[:name] end - # Returns the parent of this in the catalog. - # In case of an erroneous catalog where multiple parents have been produced, the first found (non deterministic) - # parent is returned. - # @return [???, nil] WHAT (which types can be the parent of a resource in a catalog?), or nil if there - # is no catalog. - # + # Returns the parent of this in the catalog. In case of an erroneous catalog + # where multiple parents have been produced, the first found (non + # deterministic) parent is returned. + # @return [Puppet::Type, nil] the + # containing resource or nil if there is no catalog or no containing + # resource. def parent return nil unless catalog - unless defined?(@parent) + @parent ||= if parents = catalog.adjacent(self, :direction => :in) - # We should never have more than one parent, so let's just ignore - # it if we happen to. - @parent = parents.shift + parents.shift else - @parent = nil + nil end - end - @parent end # Returns a reference to this as a string in "Type[name]" format. diff --git a/lib/puppet/type/augeas.rb b/lib/puppet/type/augeas.rb index 75c8142f2..930235f0c 100644 --- a/lib/puppet/type/augeas.rb +++ b/lib/puppet/type/augeas.rb @@ -96,52 +96,18 @@ Puppet::Type.newtype(:augeas) do desc "The changes which should be applied to the filesystem. This can be a command or an array of commands. The following commands are supported: - `set <PATH> <VALUE>` - : Sets the value `VALUE` at loction `PATH` - - - `setm <PATH> <SUB> <VALUE>` - : Sets multiple nodes (matching `SUB` relative to `PATH`) to `VALUE` - - - `rm <PATH>` - : Removes the node at location `PATH` - - - `remove <PATH>` - : Synonym for `rm` - - - `clear <PATH>` - : Sets the node at `PATH` to `NULL`, creating it if needed - - - `clearm <PATH> <SUB>` - : Sets multiple nodes (matching `SUB` relative to `PATH`) to `NULL` - - - `ins <LABEL> (before|after) <PATH>` - : Inserts an empty node `LABEL` either before or after `PATH`. - - - `insert <LABEL> <WHERE> <PATH>` - : Synonym for `ins` - - - `mv <PATH> <OTHER PATH>` - : Moves a node at `PATH` to the new location `OTHER PATH` - - - `move <PATH> <OTHER PATH>` - : Synonym for `mv` - - - `defvar <NAME> <PATH>` - : Sets Augeas variable `$NAME` to `PATH` - - - `defnode <NAME> <PATH> <VALUE>` - : Sets Augeas variable `$NAME` to `PATH`, creating it with `VALUE` if needed + * `set <PATH> <VALUE>` --- Sets the value `VALUE` at loction `PATH` + * `setm <PATH> <SUB> <VALUE>` --- Sets multiple nodes (matching `SUB` relative to `PATH`) to `VALUE` + * `rm <PATH>` --- Removes the node at location `PATH` + * `remove <PATH>` --- Synonym for `rm` + * `clear <PATH>` --- Sets the node at `PATH` to `NULL`, creating it if needed + * `clearm <PATH> <SUB>` --- Sets multiple nodes (matching `SUB` relative to `PATH`) to `NULL` + * `ins <LABEL> (before|after) <PATH>` --- Inserts an empty node `LABEL` either before or after `PATH`. + * `insert <LABEL> <WHERE> <PATH>` --- Synonym for `ins` + * `mv <PATH> <OTHER PATH>` --- Moves a node at `PATH` to the new location `OTHER PATH` + * `move <PATH> <OTHER PATH>` --- Synonym for `mv` + * `defvar <NAME> <PATH>` --- Sets Augeas variable `$NAME` to `PATH` + * `defnode <NAME> <PATH> <VALUE>` --- Sets Augeas variable `$NAME` to `PATH`, creating it with `VALUE` if needed If the `context` parameter is set, that value is prepended to any relative `PATH`s." end diff --git a/lib/puppet/type/component.rb b/lib/puppet/type/component.rb index 4783ef023..255d1a9d8 100644 --- a/lib/puppet/type/component.rb +++ b/lib/puppet/type/component.rb @@ -34,13 +34,7 @@ Puppet::Type.newtype(:component) do # Component paths are special because they function as containers. def pathbuilder if reference.type == "Class" - # 'main' is the top class, so we want to see '//' instead of - # its name. - if reference.title.to_s.downcase == "main" - myname = "" - else - myname = reference.title - end + myname = reference.title else myname = reference.to_s end diff --git a/lib/puppet/type/cron.rb b/lib/puppet/type/cron.rb index 198d6c171..198d6c171 100755..100644 --- a/lib/puppet/type/cron.rb +++ b/lib/puppet/type/cron.rb diff --git a/lib/puppet/type/exec.rb b/lib/puppet/type/exec.rb index 85724cc30..d3e83e948 100755..100644 --- a/lib/puppet/type/exec.rb +++ b/lib/puppet/type/exec.rb @@ -220,6 +220,18 @@ module Puppet end end + newparam(:umask, :required_feature => :umask) do + desc "Sets the umask to be used while executing this command" + + munge do |value| + if value =~ /^0?[0-7]{1,4}$/ + return value.to_i(8) + else + raise Puppet::Error, "The umask specification is invalid: #{value.inspect}" + end + end + end + newparam(:timeout) do desc "The maximum time the command should take. If the command takes longer than the timeout, the command is considered to have failed @@ -341,7 +353,7 @@ module Puppet # If the file exists, return false (i.e., don't run the command), # else return true def check(value) - ! FileTest.exists?(value) + ! Puppet::FileSystem::File.exist?(value) end end diff --git a/lib/puppet/type/file.rb b/lib/puppet/type/file.rb index 862ab41ba..df6af7847 100644 --- a/lib/puppet/type/file.rb +++ b/lib/puppet/type/file.rb @@ -34,6 +34,9 @@ Puppet::Type.newtype(:file) do file, the file resource will autorequire them. If Puppet is managing any parent directories of a file, the file resource will autorequire them." + feature :manages_symlinks, + "The provider can manage symbolic links." + def self.title_patterns [ [ /^(.*?)\/*\Z/m, [ [ :path ] ] ] ] end @@ -54,7 +57,12 @@ Puppet::Type.newtype(:file) do end munge do |value| - ::File.join(::File.split(::File.expand_path(value))) + if value.start_with?('//') and ::File.basename(value) == "/" + # This is a UNC path pointing to a share, so don't add a trailing slash + ::File.expand_path(value) + else + ::File.join(::File.split(::File.expand_path(value))) + end end end @@ -118,15 +126,16 @@ Puppet::Type.newtype(:file) do end newparam(:recurse) do - desc "Whether and how deeply to do recursive - management. Options are: + desc "Whether and how to do recursive file management. Options are: * `inf,true` --- Regular style recursion on both remote and local - directory structure. - * `remote` --- Descends recursively into the remote directory - but not the local directory. Allows copying of + directory structure. See `recurselimit` to specify a limit to the + recursion depth. + * `remote` --- Descends recursively into the remote (source) directory + but not the local (destination) directory. Allows copying of a few files into a directory containing many unmanaged files without scanning all the local files. + This can only be used when a source parameter is specified. * `false` --- Default of no recursion. " @@ -682,7 +691,7 @@ Puppet::Type.newtype(:file) do end @stat = begin - ::File.send(method, self[:path]) + Puppet::FileSystem::File.new(self[:path]).send(method) rescue Errno::ENOENT => error nil rescue Errno::ENOTDIR => error @@ -707,7 +716,7 @@ Puppet::Type.newtype(:file) do use_temporary_file = write_temporary_file? if use_temporary_file path = "#{self[:path]}.puppettmp_#{rand(10000)}" - path = "#{self[:path]}.puppettmp_#{rand(10000)}" while ::File.exists?(path) or ::File.symlink?(path) + path = "#{self[:path]}.puppettmp_#{rand(10000)}" while Puppet::FileSystem::File.exist?(path) or Puppet::FileSystem::File.new(path).symlink? else path = self[:path] end @@ -727,7 +736,7 @@ Puppet::Type.newtype(:file) do fail "Could not rename temporary file #{path} to #{self[:path]}: #{detail}" ensure # Make sure the created file gets removed - ::File.unlink(path) if FileTest.exists?(path) + Puppet::FileSystem::File.unlink(path) if Puppet::FileSystem::File.exist?(path) end end @@ -777,7 +786,7 @@ Puppet::Type.newtype(:file) do # @api private def remove_file(current_type, wanted_type) debug "Removing existing #{current_type} for replacement with #{wanted_type}" - ::File.unlink(self[:path]) + Puppet::FileSystem::File.unlink(self[:path]) stat_needed true end diff --git a/lib/puppet/type/file/checksum.rb b/lib/puppet/type/file/checksum.rb index 3fd37d455..3fd37d455 100755..100644 --- a/lib/puppet/type/file/checksum.rb +++ b/lib/puppet/type/file/checksum.rb diff --git a/lib/puppet/type/file/content.rb b/lib/puppet/type/file/content.rb index 5807d1885..6a2eef676 100755..100644 --- a/lib/puppet/type/file/content.rb +++ b/lib/puppet/type/file/content.rb @@ -133,6 +133,9 @@ module Puppet # Make sure we're also managing the checksum property. def should=(value) + # treat the value as a bytestring, in Ruby versions that support it, regardless of the encoding + # in which it has been supplied + value = value.clone.force_encoding(Encoding::ASCII_8BIT) if value.respond_to?(:force_encoding) @resource.newattr(:checksum) unless @resource.parameter(:checksum) super end diff --git a/lib/puppet/type/file/ensure.rb b/lib/puppet/type/file/ensure.rb index de19e10eb..d75c8f6ac 100755..100644 --- a/lib/puppet/type/file/ensure.rb +++ b/lib/puppet/type/file/ensure.rb @@ -5,17 +5,35 @@ module Puppet require 'puppet/util/symbolic_file_mode' include Puppet::Util::SymbolicFileMode - desc <<-'EOT' - Whether to create files that don't currently exist. - Possible values are `absent`, `present`, `file`, `directory`, and `link`. - Specifying `present` will match any form of file existence, and - if the file is missing will create an empty file. Specifying - `absent` will delete the file (or directory, if `recurse => true` and - `force => true`). Specifying `link` requires that you also set the `target` - attribute; note that symlinks cannot be managed on Windows. - - If you specify the path to another file as the ensure value, it is - equivalent to specifying `link` and using that path as the `target`: + desc <<-EOT + Whether the file should exist, and if so what kind of file it should be. + Possible values are `present`, `absent`, `file`, `directory`, and `link`. + + * `present` will accept any form of file existence, and will create a + normal file if the file is missing. (The file will have no content + unless the `content` or `source` attribute is used.) + * `absent` will make sure the file doesn't exist, deleting it + if necessary. + * `file` will make sure it's a normal file, and enables use of the + `content` or `source` attribute. + * `directory` will make sure it's a directory, and enables use of the + `source`, `recurse`, `recurselimit`, `ignore`, and `purge` attributes. + * `link` will make sure the file is a symlink, and **requires** that you + also set the `target` attribute. Symlinks are supported on all Posix + systems and on Windows Vista / 2008 and higher. On Windows, managing + symlinks requires puppet agent's user account to have the "Create + Symbolic Links" privilege; this can be configured in the "User Rights + Assignment" section in the Windows policy editor. By default, puppet + agent runs as the Administrator account, which does have this privilege. + + Puppet avoids destroying directories unless the `force` attribute is set + to `true`. This means that if a file is currently a directory, setting + `ensure` to anything but `directory` or `present` will cause Puppet to + skip managing the resource and log either a notice or an error. + + There is one other non-standard value for `ensure`. If you specify the + path to another file as the ensure value, it is equivalent to specifying + `link` and using that path as the `target`: # Equivalent resources: @@ -36,7 +54,7 @@ module Puppet nodefault newvalue(:absent) do - File.unlink(@resource[:path]) + Puppet::FileSystem::File.unlink(@resource[:path]) end aliasvalue(:false, :absent) @@ -61,7 +79,7 @@ module Puppet newvalue(:directory, :event => :directory_created) do mode = @resource.should(:mode) parent = File.dirname(@resource[:path]) - unless FileTest.exists? parent + unless Puppet::FileSystem::File.exist? parent raise Puppet::Error, "Cannot create #{@resource[:path]}; parent directory #{parent} does not exist" end @@ -77,7 +95,7 @@ module Puppet end - newvalue(:link, :event => :link_created) do + newvalue(:link, :event => :link_created, :required_features => :manages_symlinks) do fail "Cannot create a symlink without a target" unless property = resource.property(:target) property.retrieve property.mklink @@ -121,7 +139,7 @@ module Puppet def check basedir = File.dirname(@resource[:path]) - if ! FileTest.exists?(basedir) + if ! Puppet::FileSystem::File.exist?(basedir) raise Puppet::Error, "Can not create #{@resource.title}; parent directory does not exist" elsif ! FileTest.directory?(basedir) diff --git a/lib/puppet/type/file/group.rb b/lib/puppet/type/file/group.rb index a1ae66518..a1ae66518 100755..100644 --- a/lib/puppet/type/file/group.rb +++ b/lib/puppet/type/file/group.rb diff --git a/lib/puppet/type/file/mode.rb b/lib/puppet/type/file/mode.rb index b6b4becf2..682e744cd 100755..100644 --- a/lib/puppet/type/file/mode.rb +++ b/lib/puppet/type/file/mode.rb @@ -39,8 +39,12 @@ module Puppet * g (group's current permissions) * o (other's current permissions) - Thus, mode `0664` could be represented symbolically as either `a=r,ug+w` or - `ug=rw,o=r`. See the manual page for GNU or BSD `chmod` for more details + Thus, mode `0664` could be represented symbolically as either `a=r,ug+w` + or `ug=rw,o=r`. However, symbolic modes are more expressive than numeric + modes: a mode only affects the specified bits, so `mode => 'ug+w'` will + set the user and group write bits, without affecting any other bits. + + See the manual page for GNU or BSD `chmod` for more details on numeric and symbolic modes. On Windows, permissions are translated as follows: diff --git a/lib/puppet/type/file/owner.rb b/lib/puppet/type/file/owner.rb index 3b61b400c..3b61b400c 100755..100644 --- a/lib/puppet/type/file/owner.rb +++ b/lib/puppet/type/file/owner.rb diff --git a/lib/puppet/type/file/source.rb b/lib/puppet/type/file/source.rb index 000636b6b..7f88e692a 100755..100644 --- a/lib/puppet/type/file/source.rb +++ b/lib/puppet/type/file/source.rb @@ -1,4 +1,3 @@ - require 'puppet/file_serving/content' require 'puppet/file_serving/metadata' @@ -85,7 +84,7 @@ module Puppet def change_to_s(currentvalue, newvalue) # newvalue = "{md5}#{@metadata.checksum}" - if @resource.property(:ensure).retrieve == :absent + if resource.property(:ensure).retrieve == :absent return "creating from source #{metadata.source} with contents #{metadata.checksum}" else return "replacing from source #{metadata.source} with contents #{metadata.checksum}" @@ -111,28 +110,48 @@ module Puppet def copy_source_values devfail "Somehow got asked to copy source values without any metadata" unless metadata + # conditionally copy :checksum + if metadata.ftype != "directory" && !(metadata.ftype == "link" && metadata.links == :manage) + copy_source_value(:checksum) + end + # Take each of the stats and set them as states on the local file # if a value has not already been provided. - [:owner, :mode, :group, :checksum].each do |metadata_method| - param_name = (metadata_method == :checksum) ? :content : metadata_method + [:owner, :mode, :group].each do |metadata_method| next if metadata_method == :owner and !Puppet.features.root? - next if metadata_method == :checksum and metadata.ftype == "directory" - next if metadata_method == :checksum and metadata.ftype == "link" and metadata.links == :manage + next if metadata_method == :group and !Puppet.features.root? if Puppet.features.microsoft_windows? - next if [:owner, :group].include?(metadata_method) and !local? + # Warn on Windows if source permissions are being used and the file resource + # does not have mode owner and group all set (which would take precedence). + if [:use, :use_when_creating].include?(resource[:source_permissions]) && + (resource[:owner] == nil || resource[:group] == nil || resource[:mode] == nil) + + warning = "Copying %s from the source" << + " file on Windows is deprecated;" << + " use source_permissions => ignore." + Puppet.deprecation_warning(warning % 'owner/mode/group') + resource.debug(warning % metadata_method.to_s) + end + # But never try to copy remote owner/group on Windows + next if [:owner, :group].include?(metadata_method) && !local? end - if resource[param_name].nil? or resource[param_name] == :absent - resource[param_name] = metadata.send(metadata_method) + case resource[:source_permissions] + when :ignore + next + when :use_when_creating + next if Puppet::FileSystem::File.exist?(resource[:path]) end + + copy_source_value(metadata_method) end if resource[:ensure] == :absent # We know all we need to elsif metadata.ftype != "link" resource[:ensure] = metadata.ftype - elsif @resource[:links] == :follow + elsif resource[:links] == :follow resource[:ensure] = :present else resource[:ensure] = "link" @@ -140,10 +159,6 @@ module Puppet end end - def found? - ! (metadata.nil? or metadata.ftype.nil?) - end - attr_writer :metadata # Provide, and retrieve if necessary, the metadata for this file. Fail @@ -195,5 +210,41 @@ module Puppet def uri @uri ||= URI.parse(URI.escape(metadata.source)) end + + private + def found? + ! (metadata.nil? or metadata.ftype.nil?) + end + + def copy_source_value(metadata_method) + param_name = (metadata_method == :checksum) ? :content : metadata_method + if resource[param_name].nil? or resource[param_name] == :absent + resource[param_name] = metadata.send(metadata_method) + end + end + end + + Puppet::Type.type(:file).newparam(:source_permissions) do + desc <<-'EOT' + Whether (and how) Puppet should copy owner, group, and mode permissions from + the `source` to `file` resources when the permissions are not explicitly + specified. (In all cases, explicit permissions will take precedence.) + Valid values are `use`, `use_when_creating`, and `ignore`: + + * `use` (the default) will cause Puppet to apply the owner, group, + and mode from the `source` to any files it is managing. + * `use_when_creating` will only apply the owner, group, and mode from the + `source` when creating a file; existing files will not have their permissions + overwritten. + * `ignore` will never apply the owner, group, or mode from the `source` when + managing a file. When creating new files without explicit permissions, + the permissions they receive will depend on platform-specific behavior. + On POSIX, Puppet will use the umask of the user it is running as. On + Windows, Puppet will use the default DACL associated with the user it is + running as. + EOT + + defaultto :use + newvalues(:use, :use_when_creating, :ignore) end end diff --git a/lib/puppet/type/file/target.rb b/lib/puppet/type/file/target.rb index e1dbdeae2..08a2a97df 100644 --- a/lib/puppet/type/file/target.rb +++ b/lib/puppet/type/file/target.rb @@ -32,7 +32,7 @@ module Puppet # Create our link. def mklink - raise Puppet::Error, "Cannot symlink on Microsoft Windows" if Puppet.features.microsoft_windows? + raise Puppet::Error, "Cannot symlink on this platform version" if !provider.feature?(:manages_symlinks) target = self.should @@ -40,17 +40,17 @@ module Puppet # it doesn't determine what's removed. @resource.remove_existing(target) - raise Puppet::Error, "Could not remove existing file" if FileTest.exists?(@resource[:path]) + raise Puppet::Error, "Could not remove existing file" if Puppet::FileSystem::File.exist?(@resource[:path]) Dir.chdir(File.dirname(@resource[:path])) do Puppet::Util::SUIDManager.asuser(@resource.asuser) do mode = @resource.should(:mode) if mode Puppet::Util.withumask(000) do - File.symlink(target, @resource[:path]) + Puppet::FileSystem::File.new(target).symlink(@resource[:path]) end else - File.symlink(target, @resource[:path]) + Puppet::FileSystem::File.new(target).symlink(@resource[:path]) end end @@ -63,7 +63,7 @@ module Puppet def insync?(currentvalue) if [:nochange, :notlink].include?(self.should) or @resource.recurse? return true - elsif ! @resource.replace? and File.exists?(@resource[:path]) + elsif ! @resource.replace? and Puppet::FileSystem::File.exist?(@resource[:path]) return true else return super(currentvalue) @@ -74,7 +74,7 @@ module Puppet def retrieve if stat = @resource.stat if stat.ftype == "link" - return File.readlink(@resource[:path]) + return Puppet::FileSystem::File.new(@resource[:path]).readlink else return :notlink end diff --git a/lib/puppet/type/file/type.rb b/lib/puppet/type/file/type.rb index 38f301573..38f301573 100755..100644 --- a/lib/puppet/type/file/type.rb +++ b/lib/puppet/type/file/type.rb diff --git a/lib/puppet/type/filebucket.rb b/lib/puppet/type/filebucket.rb index cbb551464..cbb551464 100755..100644 --- a/lib/puppet/type/filebucket.rb +++ b/lib/puppet/type/filebucket.rb diff --git a/lib/puppet/type/group.rb b/lib/puppet/type/group.rb index a66a29452..cf9eb2382 100755..100644 --- a/lib/puppet/type/group.rb +++ b/lib/puppet/type/group.rb @@ -87,6 +87,24 @@ module Puppet newvalue = newvalue.join(",") super(currentvalue, newvalue) end + + def insync?(current) + if provider.respond_to?(:members_insync?) + return provider.members_insync?(current, @should) + end + + super(current) + end + + def is_to_s(currentvalue) + if provider.respond_to?(:members_to_s) + currentvalue = '' if currentvalue.nil? + return provider.members_to_s(currentvalue.split(',')) + end + + super(currentvalue) + end + alias :should_to_s :is_to_s end newparam(:auth_membership) do diff --git a/lib/puppet/type/host.rb b/lib/puppet/type/host.rb index d63b37d1a..d63b37d1a 100755..100644 --- a/lib/puppet/type/host.rb +++ b/lib/puppet/type/host.rb diff --git a/lib/puppet/type/k5login.rb b/lib/puppet/type/k5login.rb index b2fff2793..a87b3e7d8 100644 --- a/lib/puppet/type/k5login.rb +++ b/lib/puppet/type/k5login.rb @@ -37,7 +37,7 @@ Puppet::Type.newtype(:k5login) do # Does this file exist? def exists? - File.exists?(@resource[:name]) + Puppet::FileSystem::File.exist?(@resource[:name]) end # create the file @@ -51,12 +51,12 @@ Puppet::Type.newtype(:k5login) do # remove the file def destroy - File.unlink(@resource[:name]) + Puppet::FileSystem::File.unlink(@resource[:name]) end # Return the principals def principals(dummy_argument=:work_arround_for_ruby_GC_bug) - if File.exists?(@resource[:name]) + if Puppet::FileSystem::File.exist?(@resource[:name]) File.readlines(@resource[:name]).collect { |line| line.chomp } else :absent @@ -70,7 +70,7 @@ Puppet::Type.newtype(:k5login) do # Return the mode as an octal string, not as an integer def mode - "%o" % (File.stat(@resource[:name]).mode & 007777) + "%o" % (Puppet::FileSystem::File.new(@resource[:name]).stat.mode & 007777) end # Set the file mode, converting from a string to an integer. diff --git a/lib/puppet/type/mailalias.rb b/lib/puppet/type/mailalias.rb index ce7ca790b..ce7ca790b 100755..100644 --- a/lib/puppet/type/mailalias.rb +++ b/lib/puppet/type/mailalias.rb diff --git a/lib/puppet/type/maillist.rb b/lib/puppet/type/maillist.rb index 4e0542c83..4e0542c83 100755..100644 --- a/lib/puppet/type/maillist.rb +++ b/lib/puppet/type/maillist.rb diff --git a/lib/puppet/type/mount.rb b/lib/puppet/type/mount.rb index 27624e31b..acad4927d 100755..100644 --- a/lib/puppet/type/mount.rb +++ b/lib/puppet/type/mount.rb @@ -8,7 +8,11 @@ module Puppet on the value of the 'ensure' parameter. Note that if a `mount` receives an event from another resource, - it will try to remount the filesystems if `ensure` is set to `mounted`." + it will try to remount the filesystems if `ensure` is set to `mounted`. + + **Autorequires:** If Puppet is managing any parents of a mount resource --- + that is, other mount points higher up in the filesystem --- the child + mount will autorequire them." feature :refreshable, "The provider can remount the filesystem.", :methods => [:remount] @@ -268,5 +272,15 @@ module Puppet return property.value end end + + # Ensure that mounts higher up in the filesystem are mounted first + autorequire(:mount) do + dependencies = [] + Pathname.new(@parameters[:name].value).ascend do |parent| + dependencies.unshift parent.to_s + end + dependencies[0..-2] + end + end end diff --git a/lib/puppet/type/package.rb b/lib/puppet/type/package.rb index 5500f4499..e7ea1125b 100644 --- a/lib/puppet/type/package.rb +++ b/lib/puppet/type/package.rb @@ -58,7 +58,7 @@ module Puppet retrieve by specifying a version number or `latest` as the ensure value. On packaging systems that manage configuration files separately from "normal" system files, you can uninstall config files by - specifying `purged` as the ensure value. + specifying `purged` as the ensure value. This defaults to `installed`. EOT attr_accessor :latest @@ -225,6 +225,12 @@ module Puppet " isnamevar + + validate do |value| + if !value.is_a?(String) + raise ArgumentError, "Name must be a String not #{value.class}" + end + end end newparam(:source) do diff --git a/lib/puppet/type/port.rb b/lib/puppet/type/port.rb index e19988515..e19988515 100755..100644 --- a/lib/puppet/type/port.rb +++ b/lib/puppet/type/port.rb diff --git a/lib/puppet/type/schedule.rb b/lib/puppet/type/schedule.rb index e1586cbdb..d67831e89 100755..100644 --- a/lib/puppet/type/schedule.rb +++ b/lib/puppet/type/schedule.rb @@ -356,10 +356,10 @@ module Puppet of the range, not necessarily the day that it is when it matches. For example, consider this schedule: - schedule { 'maintenance_window': - range => '22:00 - 04:00', - weekday => 'Saturday', - } + schedule { 'maintenance_window': + range => '22:00 - 04:00', + weekday => 'Saturday', + } This will match at 11 PM on Saturday and 2 AM on Sunday, but not at 2 AM on Saturday. @@ -418,6 +418,11 @@ module Puppet def self.mkdefaultschedules result = [] + unless Puppet[:default_schedules] + Puppet.debug "Not creating default schedules: default_schedules is false" + return result + end + Puppet.debug "Creating default schedules" result << self.new( diff --git a/lib/puppet/type/service.rb b/lib/puppet/type/service.rb index 0a74649cf..e4876664e 100644 --- a/lib/puppet/type/service.rb +++ b/lib/puppet/type/service.rb @@ -180,7 +180,7 @@ module Puppet automatically, usually by looking for the service in the process table. - [lsb-exit-codes]: http://refspecs.freestandards.org/LSB_3.1.1/LSB-Core-generic/LSB-Core-generic/iniscrptact.html" + [lsb-exit-codes]: http://refspecs.linuxfoundation.org/LSB_4.1.0/LSB-Core-generic/LSB-Core-generic/iniscrptact.html" end newparam(:stop) do diff --git a/lib/puppet/type/sshkey.rb b/lib/puppet/type/sshkey.rb index 41948ed98..41948ed98 100755..100644 --- a/lib/puppet/type/sshkey.rb +++ b/lib/puppet/type/sshkey.rb diff --git a/lib/puppet/type/tidy.rb b/lib/puppet/type/tidy.rb index 8297cf938..83ac3322d 100755..100644 --- a/lib/puppet/type/tidy.rb +++ b/lib/puppet/type/tidy.rb @@ -312,7 +312,7 @@ Puppet::Type.newtype(:tidy) do def stat(path) begin - ::File.lstat(path) + Puppet::FileSystem::File.new(path).lstat rescue Errno::ENOENT => error info "File does not exist" return nil diff --git a/lib/puppet/type/user.rb b/lib/puppet/type/user.rb index 9ec6e922f..d0ca0f78c 100755..100644 --- a/lib/puppet/type/user.rb +++ b/lib/puppet/type/user.rb @@ -124,8 +124,9 @@ module Puppet newproperty(:gid) do desc "The user's primary group. Can be specified numerically or by name. - Note that users on Windows systems do not have a primary group; manage groups - with the `groups` attribute instead." + This attribute is not supported on Windows systems; use the `groups` + attribute instead. (On Windows, designating a primary group is only + meaningful for domain accounts, which Puppet does not currently manage.)" munge do |value| if value.is_a?(String) and value =~ /^[-0-9]+$/ @@ -163,6 +164,9 @@ module Puppet newproperty(:comment) do desc "A description of the user. Generally the user's full name." + munge do |v| + v.respond_to?(:encode) ? v.encode(Encoding::ASCII_8BIT) : v + end end newproperty(:shell) do diff --git a/lib/puppet/type/yumrepo.rb b/lib/puppet/type/yumrepo.rb index bd52121c9..f71736045 100644 --- a/lib/puppet/type/yumrepo.rb +++ b/lib/puppet/type/yumrepo.rb @@ -170,7 +170,7 @@ module Puppet unless Puppet[:noop] target_mode = 0644 # FIXME: should be configurable inifile.each_file do |file| - current_mode = ::File.stat(file).mode & 0777 + current_mode = Puppet::FileSystem::File.new(file).stat.mode & 0777 unless current_mode == target_mode Puppet::info "changing mode of #{file} from %03o to %03o" % [current_mode, target_mode] ::File.chmod(target_mode, file) @@ -332,7 +332,7 @@ module Puppet end newproperty(:cost, :parent => Puppet::IniProperty) do - desc "Cost of this repository.\n#{ABSENT_DOC}" + desc "Cost of this repository. #{ABSENT_DOC}" newvalue(:absent) { self.should = :absent } newvalue(%r{\d+}) { } end @@ -364,28 +364,30 @@ module Puppet newproperty(:sslcacert, :parent => Puppet::IniProperty) do desc "Path to the directory containing the databases of the - certificate authorities yum should use to verify SSL certificates.\n#{ABSENT_DOC}" + certificate authorities yum should use to verify SSL certificates. + #{ABSENT_DOC}" newvalue(:absent) { self.should = :absent } newvalue(/.*/) { } end newproperty(:sslverify, :parent => Puppet::IniProperty) do desc "Should yum verify SSL certificates/hosts at all. - Possible values are 'True' or 'False'.\n#{ABSENT_DOC}" + Possible values are 'True' or 'False'. + #{ABSENT_DOC}" newvalue(:absent) { self.should = :absent } newvalue(%r(True|False)) { } end newproperty(:sslclientcert, :parent => Puppet::IniProperty) do desc "Path to the SSL client certificate yum should use to connect - to repos/remote sites.\n#{ABSENT_DOC}" + to repos/remote sites. #{ABSENT_DOC}" newvalue(:absent) { self.should = :absent } newvalue(/.*/) { } end newproperty(:sslclientkey, :parent => Puppet::IniProperty) do desc "Path to the SSL client key yum should use to connect - to repos/remote sites.\n#{ABSENT_DOC}" + to repos/remote sites. #{ABSENT_DOC}" newvalue(:absent) { self.should = :absent } newvalue(/.*/) { } end diff --git a/lib/puppet/type/zpool.rb b/lib/puppet/type/zpool.rb index 043deecc4..043deecc4 100755..100644 --- a/lib/puppet/type/zpool.rb +++ b/lib/puppet/type/zpool.rb diff --git a/lib/puppet/util.rb b/lib/puppet/util.rb index c1a82340e..4c04f37aa 100644 --- a/lib/puppet/util.rb +++ b/lib/puppet/util.rb @@ -1,12 +1,9 @@ # A module to collect utility functions. require 'English' -require 'puppet/external/lock' require 'puppet/error' require 'puppet/util/execution_stub' require 'uri' -require 'sync' -require 'monitor' require 'tempfile' require 'pathname' require 'ostruct' @@ -26,9 +23,6 @@ module Util extend Puppet::Util::SymbolicFileMode - @@sync_objects = {}.extend MonitorMixin - - def self.activerecord_version if (defined?(::ActiveRecord) and defined?(::ActiveRecord::VERSION) and defined?(::ActiveRecord::VERSION::MAJOR) and defined?(::ActiveRecord::VERSION::MINOR)) ([::ActiveRecord::VERSION::MAJOR, ::ActiveRecord::VERSION::MINOR].join('.').to_f) @@ -67,20 +61,6 @@ module Util end - def self.synchronize_on(x,type) - sync_object,users = 0,1 - begin - @@sync_objects.synchronize { - (@@sync_objects[x] ||= [Sync.new,0])[users] += 1 - } - @@sync_objects[x][sync_object].synchronize(type) { yield } - ensure - @@sync_objects.synchronize { - @@sync_objects.delete(x) unless (@@sync_objects[x][users] -= 1) > 0 - } - end - end - # Change the process to a different user def self.chuser if group = Puppet[:group] @@ -154,7 +134,6 @@ module Util end end - def benchmark(*args) msg = args.pop level = args.pop @@ -187,6 +166,7 @@ module Util yield end end + module_function :benchmark # Resolve a path for an executable to the absolute path. This tries to behave # in the same manner as the unix `which` command and uses the `PATH` @@ -270,7 +250,7 @@ module Util if Puppet.features.microsoft_windows? path = path.gsub(/\\/, '/') - if unc = /^\/\/([^\/]+)(\/[^\/]+)/.match(path) + if unc = /^\/\/([^\/]+)(\/.+)/.match(path) params[:host] = unc[1] path = unc[2] elsif path =~ /^[a-z]:\//i @@ -320,13 +300,6 @@ module Util end module_function :safe_posix_fork - # Create an exclusive lock. - def threadlock(resource, type = Sync::EX) - Puppet::Util.synchronize_on(resource,type) { yield } - end - - module_function :benchmark - def memory unless defined?(@pmap) @pmap = which('pmap') @@ -361,6 +334,7 @@ module Util # Because IO#binread is only available in 1.9 def binread(file) + Puppet.deprecation_warning("Puppet::Util.binread is deprecated. Read the file without this method as it will be removed in a future version.") File.open(file, 'rb') { |f| f.read } end module_function :binread @@ -467,7 +441,7 @@ module Util # This might race, but there are enough possible cases that there # isn't a good, solid "better" way to do this, and the next call # should fail in the same way anyhow. - raise if have_retried or File.exist?(file) + raise if have_retried or Puppet::FileSystem::File.exist?(file) have_retried = true # OK, so, we can't replace a file that doesn't exist, so let us put @@ -499,7 +473,6 @@ module Util end module_function :replace_file - # Executes a block of code, wrapped with some special exception handling. Causes the ruby interpreter to # exit if the block throws an exception. # diff --git a/lib/puppet/util/adsi.rb b/lib/puppet/util/adsi.rb index a4b02d537..98639eabd 100644 --- a/lib/puppet/util/adsi.rb +++ b/lib/puppet/util/adsi.rb @@ -41,6 +41,11 @@ module Puppet::Util::ADSI "winmgmts:{impersonationLevel=impersonate}!//#{host}/root/cimv2" end + def sid_uri(sid) + raise Puppet::Error.new( "Must use a valid SID object" ) if !sid.kind_of?(Win32::Security::SID) + "WinNT://#{sid.to_s}" + end + def uri(resource_name, resource_type, host = '.') "#{computer_uri(host)}/#{resource_name},#{resource_type}" end @@ -64,22 +69,40 @@ module Puppet::Util::ADSI extend Enumerable attr_accessor :native_user - attr_reader :name + attr_reader :name, :sid def initialize(name, native_user = nil) @name = name @native_user = native_user end + def self.parse_name(name) + if name =~ /\// + raise Puppet::Error.new( "Value must be in DOMAIN\\user style syntax" ) + end + + matches = name.scan(/((.*)\\)?(.*)/) + domain = matches[0][1] || '.' + account = matches[0][2] + + return account, domain + end + def native_user - @native_user ||= Puppet::Util::ADSI.connect(uri) + @native_user ||= Puppet::Util::ADSI.connect(self.class.uri(*self.class.parse_name(@name))) + end + + def sid + @sid ||= Puppet::Util::Windows::Security.octet_string_to_sid_object(native_user.objectSID) end def self.uri(name, host = '.') + host = '.' if ['NT AUTHORITY', 'BUILTIN', Socket.gethostname].include?(host) + Puppet::Util::ADSI.uri(name, 'user', host) end def uri - self.class.uri(name) + self.class.uri(sid.account, sid.domain) end def self.logon(name, password) @@ -131,14 +154,14 @@ module Puppet::Util::ADSI def add_to_groups(*group_names) group_names.each do |group_name| - Puppet::Util::ADSI::Group.new(group_name).add_member(@name) + Puppet::Util::ADSI::Group.new(group_name).add_member_sids(sid) end end alias add_to_group add_to_groups def remove_from_groups(*group_names) group_names.each do |group_name| - Puppet::Util::ADSI::Group.new(group_name).remove_member(@name) + Puppet::Util::ADSI::Group.new(group_name).remove_member_sids(sid) end end alias remove_from_group remove_from_groups @@ -167,7 +190,7 @@ module Puppet::Util::ADSI end def self.exists?(name) - Puppet::Util::ADSI::connectable?(User.uri(name)) + Puppet::Util::ADSI::connectable?(User.uri(*User.parse_name(name))) end def self.delete(name) @@ -175,7 +198,7 @@ module Puppet::Util::ADSI end def self.each(&block) - wql = Puppet::Util::ADSI.execquery("select name from win32_useraccount") + wql = Puppet::Util::ADSI.execquery('select name from win32_useraccount where localaccount = "TRUE"') users = [] wql.each do |u| @@ -233,20 +256,44 @@ module Puppet::Util::ADSI self end - def add_members(*names) - names.each do |name| - native_group.Add(Puppet::Util::ADSI::User.uri(name, Puppet::Util::ADSI.computer_name)) + def self.name_sid_hash(names) + return [] if names.nil? or names.empty? + + sids = names.map do |name| + sid = Puppet::Util::Windows::Security.name_to_sid_object(name) + raise Puppet::Error.new( "Could not resolve username: #{name}" ) if !sid + [sid.to_s, sid] end + + Hash[ sids ] + end + + def add_members(*names) + Puppet.deprecation_warning('Puppet::Util::ADSI::Group#add_members is deprecated; please use Puppet::Util::ADSI::Group#add_member_sids') + sids = self.class.name_sid_hash(names) + add_member_sids(*sids.values) end alias add_member add_members def remove_members(*names) - names.each do |name| - native_group.Remove(Puppet::Util::ADSI::User.uri(name, Puppet::Util::ADSI.computer_name)) - end + Puppet.deprecation_warning('Puppet::Util::ADSI::Group#remove_members is deprecated; please use Puppet::Util::ADSI::Group#remove_member_sids') + sids = self.class.name_sid_hash(names) + remove_member_sids(*sids.values) end alias remove_member remove_members + def add_member_sids(*sids) + sids.each do |sid| + native_group.Add(Puppet::Util::ADSI.sid_uri(sid)) + end + end + + def remove_member_sids(*sids) + sids.each do |sid| + native_group.Remove(Puppet::Util::ADSI.sid_uri(sid)) + end + end + def members # WIN32OLE objects aren't enumerable, so no map members = [] @@ -254,18 +301,27 @@ module Puppet::Util::ADSI members end + def member_sids + sids = [] + native_group.Members.each do |m| + sids << Puppet::Util::Windows::Security.octet_string_to_sid_object(m.objectSID) + end + sids + end + def set_members(desired_members) return if desired_members.nil? or desired_members.empty? - current_members = self.members + current_hash = Hash[ self.member_sids.map { |sid| [sid.to_s, sid] } ] + desired_hash = self.class.name_sid_hash(desired_members) # First we add all missing members - members_to_add = desired_members - current_members - add_members(*members_to_add) + members_to_add = (desired_hash.keys - current_hash.keys).map { |sid| desired_hash[sid] } + add_member_sids(*members_to_add) # Then we remove all extra members - members_to_remove = current_members - desired_members - remove_members(*members_to_remove) + members_to_remove = (current_hash.keys - desired_hash.keys).map { |sid| current_hash[sid] } + remove_member_sids(*members_to_remove) end def self.create(name) @@ -283,7 +339,7 @@ module Puppet::Util::ADSI end def self.each(&block) - wql = Puppet::Util::ADSI.execquery( "select name from win32_group" ) + wql = Puppet::Util::ADSI.execquery( 'select name from win32_group where localaccount = "TRUE"' ) groups = [] wql.each do |g| diff --git a/lib/puppet/util/autoload.rb b/lib/puppet/util/autoload.rb index fc407a971..82e0560e8 100644 --- a/lib/puppet/util/autoload.rb +++ b/lib/puppet/util/autoload.rb @@ -85,7 +85,7 @@ class Puppet::Util::Autoload # returns nil if no file is found def get_file(name, env=nil) name = name + '.rb' unless name =~ /\.rb$/ - path = search_directories(env).find { |dir| File.exist?(File.join(dir, name)) } + path = search_directories(env).find { |dir| Puppet::FileSystem::File.exist?(File.join(dir, name)) } path and File.join(path, name) end @@ -112,7 +112,7 @@ class Puppet::Util::Autoload # We're using a per-thread cache of module directories so that we don't # scan the filesystem each time we try to load something. This is reset # at the beginning of compilation and at the end of an agent run. - Thread.current[:env_module_directories] ||= {} + $env_module_directories ||= {} # This is a little bit of a hack. Basically, the autoloader is being @@ -136,7 +136,7 @@ class Puppet::Util::Autoload # --cprice 2012-03-16 if Puppet.settings.app_defaults_initialized? # if the app defaults have been initialized then it should be safe to access the module path setting. - Thread.current[:env_module_directories][real_env] ||= real_env.modulepath.collect do |dir| + $env_module_directories[real_env] ||= real_env.modulepath.collect do |dir| Dir.entries(dir).reject { |f| f =~ /^\./ }.collect { |f| File.join(dir, f) } end.flatten.collect { |d| File.join(d, "lib") }.find_all do |d| FileTest.directory?(d) diff --git a/lib/puppet/util/backups.rb b/lib/puppet/util/backups.rb index b1daf78fa..8ae14c190 100644 --- a/lib/puppet/util/backups.rb +++ b/lib/puppet/util/backups.rb @@ -10,7 +10,7 @@ module Puppet::Util::Backups # let the path be specified file ||= self[:path] - return true unless FileTest.exists?(file) + return true unless Puppet::FileSystem::File.exist?(file) return(self.bucket ? perform_backup_with_bucket(file) : perform_backup_with_backuplocal(file, self[:backup])) end @@ -19,7 +19,7 @@ module Puppet::Util::Backups def perform_backup_with_bucket(fileobj) file = (fileobj.class == String) ? fileobj : fileobj.name - case File.lstat(file).ftype + case Puppet::FileSystem::File.new(file).lstat.ftype when "directory" # we don't need to backup directories when recurse is on return true if self[:recurse] @@ -58,7 +58,7 @@ module Puppet::Util::Backups end begin - stat = File.send(method, newfile) + stat = Puppet::FileSystem::File.new(newfile).send(method) rescue Errno::ENOENT return end @@ -70,7 +70,7 @@ module Puppet::Util::Backups info "Removing old backup of type #{stat.ftype}" begin - File.unlink(newfile) + Puppet::FileSystem::File.unlink(newfile) rescue => detail message = "Could not remove old backup: #{detail}" self.log_exception(detail, message) diff --git a/lib/puppet/util/cacher.rb b/lib/puppet/util/cacher.rb index 136c9973e..24017de32 100644 --- a/lib/puppet/util/cacher.rb +++ b/lib/puppet/util/cacher.rb @@ -1,5 +1,3 @@ -require 'monitor' - module Puppet::Util::Cacher # Our module has been extended in a class; we can only add the Instance methods, # which become *class* methods in the class. @@ -33,10 +31,8 @@ module Puppet::Util::Cacher define_method(name.to_s + "=") do |value| # Make sure the cache timestamp is set - value_cache.synchronize do - value_cache[name] = value - set_expiration(name) - end + value_cache[name] = value + set_expiration(name) end end @@ -55,13 +51,11 @@ module Puppet::Util::Cacher private def cached_value(name) - value_cache.synchronize do - if value_cache[name].nil? or expired_by_ttl?(name) - value_cache[name] = send("init_#{name}") - set_expiration(name) - end - value_cache[name] + if value_cache[name].nil? or expired_by_ttl?(name) + value_cache[name] = send("init_#{name}") + set_expiration(name) end + value_cache[name] end def expired_by_ttl?(name) @@ -74,7 +68,7 @@ module Puppet::Util::Cacher end def value_cache - @value_cache ||= {}.extend(MonitorMixin) + @value_cache ||= {} end end end diff --git a/lib/puppet/util/checksums.rb b/lib/puppet/util/checksums.rb index a505bfc72..1f28fe8a7 100644 --- a/lib/puppet/util/checksums.rb +++ b/lib/puppet/util/checksums.rb @@ -56,7 +56,7 @@ module Puppet::Util::Checksums # Return the :mtime timestamp of a file. def mtime_file(filename) - File.stat(filename).send(:mtime) + Puppet::FileSystem::File.new(filename).stat.send(:mtime) end # by definition this doesn't exist @@ -102,7 +102,7 @@ module Puppet::Util::Checksums # Return the :ctime of a file. def ctime_file(filename) - File.stat(filename).send(:ctime) + Puppet::FileSystem::File.new(filename).stat.send(:ctime) end alias :ctime_stream :mtime_stream diff --git a/lib/puppet/util/classgen.rb b/lib/puppet/util/classgen.rb index 8785f87b3..ee1ea5f46 100644 --- a/lib/puppet/util/classgen.rb +++ b/lib/puppet/util/classgen.rb @@ -1,3 +1,5 @@ +require 'puppet/util/methodhelper' + module Puppet class ConstantAlreadyDefined < Error; end class SubclassAlreadyDefined < Error; end @@ -67,7 +69,7 @@ module Puppet::Util::ClassGen options = symbolize_options(options) const = genconst_string(name, options) retval = false - if const_defined?(const) + if is_constant_defined?(const) remove_const(const) retval = true end diff --git a/lib/puppet/util/colors.rb b/lib/puppet/util/colors.rb index 37bcddf4a..a10e240a1 100644 --- a/lib/puppet/util/colors.rb +++ b/lib/puppet/util/colors.rb @@ -82,6 +82,7 @@ module Puppet::Util::Colors if Puppet::Util::Platform.windows? # We're on windows, need win32console for color to work begin + require 'Win32API' require 'win32console' require 'windows/wide_string' diff --git a/lib/puppet/util/command_line.rb b/lib/puppet/util/command_line.rb index 1612691e8..a334ef8db 100644 --- a/lib/puppet/util/command_line.rb +++ b/lib/puppet/util/command_line.rb @@ -13,6 +13,7 @@ require 'puppet' require 'puppet/util' require "puppet/util/plugins" require "puppet/util/rubygems" +require "puppet/util/limits" module Puppet module Util @@ -20,6 +21,8 @@ module Puppet # is basically where the bootstrapping process / lifecycle of an app # begins. class CommandLine + include Puppet::Util::Limits + OPTION_OR_MANIFEST_FILE = /^-|\.pp$|\.rb$/ # @param zero [String] the name of the executable @@ -83,6 +86,8 @@ module Puppet Puppet.initialize_settings(args) end + setpriority(Puppet[:priority]) + find_subcommand.run end diff --git a/lib/puppet/util/docs.rb b/lib/puppet/util/docs.rb index 0e9277f97..0c854b76c 100644 --- a/lib/puppet/util/docs.rb +++ b/lib/puppet/util/docs.rb @@ -20,10 +20,10 @@ module Puppet::Util::Docs def doc extra = methods.find_all { |m| m.to_s =~ /^dochook_.+/ }.sort.collect { |m| self.send(m) - }.delete_if {|r| r.nil? }.join(" ") + }.delete_if {|r| r.nil? }.collect {|r| "* #{r}"}.join("\n") if @doc - @doc + (extra.empty? ? '' : "\n\n" + extra) + scrub(@doc) + (extra.empty? ? '' : "\n\n#{extra}") else extra end @@ -63,6 +63,7 @@ module Puppet::Util::Docs str + "\n" end + # There is nothing that would ever set this. It gets read in reference/type.rb, but will never have any value but nil. attr_reader :nodoc def nodoc? nodoc @@ -89,33 +90,38 @@ module Puppet::Util::Docs str << "\n" end - # Handle the inline indentation in the docs. + # Strip indentation and trailing whitespace from embedded doc fragments. + # + # Multi-line doc fragments are sometimes indented in order to preserve the + # formatting of the code they're embedded in. Since indents are syntactic + # elements in Markdown, we need to make sure we remove any indent that was + # added solely to preserve surrounding code formatting, but LEAVE any indent + # that delineates a Markdown element (code blocks, multi-line bulleted list + # items). We can do this by removing the *least common indent* from each line. + # + # Least common indent is defined as follows: + # + # * Find the smallest amount of leading space on any line... + # * ...excluding the first line (which may have zero indent without affecting + # the common indent)... + # * ...and excluding lines that consist solely of whitespace. + # * The least common indent may be a zero-length string, if the fragment is + # not indented to match code. + # * If there are hard tabs for some dumb reason, we assume they're at least + # consistent within this doc fragment. + # + # See tests in spec/unit/util/docs_spec.rb for examples. def scrub(text) - # Stupid markdown - #text = text.gsub("<%=", "<%=") - # For text with no carriage returns, there's nothing to do. - return text if text !~ /\n/ - indent = nil - - # If we can match an indentation, then just remove that same level of - # indent from every line. However, ignore any indentation on the - # first line, since that can be inconsistent. - text = text.lstrip - text.gsub!(/^([\t]+)/) { |s| " "*8*s.length; } # Expand leading tabs - # Find first non-empty line after the first line: - line2start = (text =~ /(\n?\s*\n)/) - line2start += $1.length - if (text[line2start..-1] =~ /^([ ]+)\S/) == 0 - indent = Regexp.quote($1) - begin - return text.gsub(/^#{indent}/,'') - rescue => detail - Puppet.log_exception(detail) - end - else - return text + # One-liners are easy! + return text.strip if text !~ /\n/ + excluding_first_line = text.partition("\n").last + indent = excluding_first_line.scan(/^[ \t]*(?=\S)/).min || '' # prevent nil + # Clean hanging indent, if any + if indent.length > 0 + text = text.gsub(/^#{indent}/, '') end - + # Clean trailing space + text.lines.map{|line|line.rstrip}.join("\n").rstrip end module_function :scrub diff --git a/lib/puppet/util/execution.rb b/lib/puppet/util/execution.rb index 5f0089303..e931816f6 100644 --- a/lib/puppet/util/execution.rb +++ b/lib/puppet/util/execution.rb @@ -1,15 +1,33 @@ module Puppet require 'rbconfig' - # A command failed to execute. require 'puppet/error' + # A command failed to execute. + # @api public class ExecutionFailure < Puppet::Error end +end # This module defines methods for execution of system commands. It is intented for inclusion # in classes that needs to execute system commands. # @api public -module Util::Execution +module Puppet::Util::Execution + + # This is the full output from a process. The object itself (a String) is the + # stdout of the process. + # + # @api public + class ProcessOutput < String + # @return [Integer] The exit status of the process + # @api public + attr_reader :exitstatus + + # @api private + def initialize(value,exitstatus) + super(value) + @exitstatus = exitstatus + end + end # Executes the provided command with STDIN connected to a pipe, yielding the # pipe object. @@ -27,17 +45,12 @@ module Util::Execution # @yield [pipe] to a block executing a subprocess # @yieldparam pipe [IO] the opened pipe # @yieldreturn [String] the output to return - # @raise [ExecutionFailure] if the executed chiled process did not exit with status == 0 and `failonfail` is + # @raise [Puppet::ExecutionFailure] if the executed chiled process did not exit with status == 0 and `failonfail` is # `true`. # @return [String] a string with the output from the subprocess executed by the given block + # @api public # def self.execpipe(command, failonfail = true) - if respond_to? :debug - debug "Executing '#{command}'" - else - Puppet.debug "Executing '#{command}'" - end - # Paste together an array with spaces. We used to paste directly # together, no spaces, which made for odd invocations; the user had to # include whitespace between arguments. @@ -46,13 +59,20 @@ module Util::Execution # shell anyhow, while no spaces makes for a small developer cost every # time this is invoked. --daniel 2012-02-13 command_str = command.respond_to?(:join) ? command.join(' ') : command + + if respond_to? :debug + debug "Executing '#{command_str}'" + else + Puppet.debug "Executing '#{command_str}'" + end + output = open("| #{command_str} 2>&1") do |pipe| yield pipe end if failonfail unless $CHILD_STATUS == 0 - raise ExecutionFailure, output + raise Puppet::ExecutionFailure, output end end @@ -62,10 +82,11 @@ module Util::Execution # Wraps execution of {execute} with mapping of exception to given exception (and output as argument). # @raise [exception] under same conditions as {execute}, but raises the given `exception` with the output as argument # @return (see execute) + # @api public def self.execfail(command, exception) output = execute(command) return output - rescue ExecutionFailure + rescue Puppet::ExecutionFailure raise exception, output end @@ -80,8 +101,8 @@ module Util::Execution # @param options [Hash] a Hash of options # @option options [Boolean] :failonfail if this value is set to true, then this method will raise an error if the # command is not executed successfully. - # @option options [?] :uid (nil) the user id of the user that the process should be run as - # @option options [?] :gid (nil) the group id of the group that the process should be run as + # @option options [Integer, String] :uid (nil) the user id of the user that the process should be run as + # @option options [Integer, String] :gid (nil) the group id of the group that the process should be run as # @option options [Boolean] :combine sets whether or not to combine stdout/stderr in the output # @option options [String] :stdinfile (nil) sets a file that can be used for stdin. Passing a string for stdin is not currently # supported. @@ -92,13 +113,16 @@ module Util::Execution # Passing in a value of false for this option will allow the command to be executed using the user/system locale. # @option options [Hash<{String => String}>] :custom_environment ({}) a hash of key/value pairs to set as environment variables for the duration # of the command. - # @return [String] output as specified by options + # @return [Puppet::Util::Execution::ProcessOutput] output as specified by options + # @raise [Puppet::ExecutionFailure] if the executed chiled process did not exit with status == 0 and `failonfail` is + # `true`. # @note Unfortunately, the default behavior for failonfail and combine (since # 0.22.4 and 0.24.7, respectively) depend on whether options are specified # or not. If specified, then failonfail and combine default to false (even # when the options specified are neither failonfail nor combine). If no # options are specified, then failonfail and combine default to true. # @comment See commits efe9a833c and d32d7f30 + # @api public # def self.execute(command, options = NoOptionsSpecified) # specifying these here rather than in the method signature to allow callers to pass in a partial @@ -147,8 +171,8 @@ module Util::Execution begin exit_status = Puppet::Util::Windows::Process.wait_process(process_info.process_handle) ensure - Process.CloseHandle(process_info.process_handle) - Process.CloseHandle(process_info.thread_handle) + Puppet::Util::Windows::Process.CloseHandle(process_info.process_handle) + Puppet::Util::Windows::Process.CloseHandle(process_info.thread_handle) end end @@ -161,15 +185,16 @@ module Util::Execution end if options[:failonfail] and exit_status != 0 - raise ExecutionFailure, "Execution of '#{str}' returned #{exit_status}: #{output}" + raise Puppet::ExecutionFailure, "Execution of '#{str}' returned #{exit_status}: #{output}" end - output + Puppet::Util::Execution::ProcessOutput.new(output || '', exit_status) end # Returns the path to the ruby executable (available via Config object, even if # it's not in the PATH... so this is slightly safer than just using Puppet::Util.which) # @return [String] the path to the Ruby executable + # @api private # def self.ruby_path() File.join(RbConfig::CONFIG['bindir'], @@ -261,7 +286,7 @@ module Util::Execution # about a race condition because all of the places that we call this from are preceded by a call to "waitpid2", # meaning that the processes responsible for writing the file have completed before we get here.) 2.times do |try| - if File.exists?(stdout.path) + if Puppet::FileSystem::File.exist?(stdout.path) stdout.open begin return stdout.read @@ -279,4 +304,3 @@ module Util::Execution end private_class_method :wait_for_output end -end diff --git a/lib/puppet/util/filetype.rb b/lib/puppet/util/filetype.rb index c65f8026e..7f43f8c10 100755..100644 --- a/lib/puppet/util/filetype.rb +++ b/lib/puppet/util/filetype.rb @@ -98,12 +98,12 @@ class Puppet::Util::FileType newfiletype(:flat) do # Back the file up before replacing it. def backup - bucket.backup(@path) if File.exists?(@path) + bucket.backup(@path) if Puppet::FileSystem::File.exist?(@path) end # Read the file. def read - if File.exist?(@path) + if Puppet::FileSystem::File.exist?(@path) File.read(@path) else return nil @@ -112,7 +112,7 @@ class Puppet::Util::FileType # Remove the file. def remove - File.unlink(@path) if File.exist?(@path) + Puppet::FileSystem::File.unlink(@path) if Puppet::FileSystem::File.exist?(@path) end # Overwrite the file. diff --git a/lib/puppet/util/instance_loader.rb b/lib/puppet/util/instance_loader.rb index 7c2e18486..c6d6b71d4 100755..100644 --- a/lib/puppet/util/instance_loader.rb +++ b/lib/puppet/util/instance_loader.rb @@ -42,9 +42,9 @@ module Puppet::Util::InstanceLoader # Use this method so they all get loaded loaded_instances(type).sort { |a,b| a.to_s <=> b.to_s }.each do |name| mod = self.loaded_instance(name) - docs += "#{name}\n#{"-" * name.to_s.length}\n" + docs << "#{name}\n#{"-" * name.to_s.length}\n" - docs += Puppet::Util::Docs.scrub(mod.doc) + "\n\n" + docs << Puppet::Util::Docs.scrub(mod.doc) << "\n\n" end docs diff --git a/lib/puppet/util/instrumentation.rb b/lib/puppet/util/instrumentation.rb index eb522437e..94a75ac03 100644 --- a/lib/puppet/util/instrumentation.rb +++ b/lib/puppet/util/instrumentation.rb @@ -5,7 +5,6 @@ require 'puppet/util/instance_loader' class Puppet::Util::Instrumentation extend Puppet::Util::ClassGen extend Puppet::Util::InstanceLoader - extend MonitorMixin # we're using a ruby lazy autoloader to prevent a loop when requiring listeners # since this class sets up an indirection which is also used in Puppet::Indirector::Indirection @@ -71,11 +70,9 @@ class Puppet::Util::Instrumentation end def self.each_listener(label) - synchronize { - @listeners_of[label] ||= @listeners.select do |k,l| - l.listen_to?(label) - end - }.each do |l| + @listeners_of[label] ||= @listeners.select do |k,l| + l.listen_to?(label) + end.each do |l| yield l end end @@ -105,67 +102,51 @@ class Puppet::Util::Instrumentation end def self.subscribe(listener, label_pattern, event) - synchronize { - raise "Listener #{listener.name} is already subscribed" if @listeners.include?(listener.name) - Puppet.debug "registering instrumentation listener #{listener.name}" - @listeners[listener.name] = Listener.new(listener, label_pattern, event) - listener.subscribed if listener.respond_to?(:subscribed) - rehash - } + raise "Listener #{listener.name} is already subscribed" if @listeners.include?(listener.name) + Puppet.debug "registering instrumentation listener #{listener.name}" + @listeners[listener.name] = Listener.new(listener, label_pattern, event) + listener.subscribed if listener.respond_to?(:subscribed) + rehash end def self.unsubscribe(listener) - synchronize { - Puppet.warning("#{listener.name} hasn't been registered but asked to be unregistered") unless @listeners.include?(listener.name) - Puppet.info "unregistering instrumentation listener #{listener.name}" - @listeners.delete(listener.name) - listener.unsubscribed if listener.respond_to?(:unsubscribed) - rehash - } + Puppet.warning("#{listener.name} hasn't been registered but asked to be unregistered") unless @listeners.include?(listener.name) + Puppet.info "unregistering instrumentation listener #{listener.name}" + @listeners.delete(listener.name) + listener.unsubscribed if listener.respond_to?(:unsubscribed) + rehash end def self.init # let's init our probe indirection require 'puppet/util/instrumentation/indirection_probe' - synchronize { - @listeners ||= {} - @listeners_of ||= {} - instance_loader(:listener).loadall - } + @listeners ||= {} + @listeners_of ||= {} + instance_loader(:listener).loadall end def self.clear - synchronize { - @listeners = {} - @listeners_of = {} - @id = 0 - } + @listeners = {} + @listeners_of = {} + @id = 0 end def self.[](key) - synchronize { - @listeners[key.intern] - } + @listeners[key.intern] end def self.[]=(key, value) - synchronize { - @listeners[key.intern] = value - rehash - } + @listeners[key.intern] = value + rehash end private - # should be called only under the guard - # self.synchronize def self.rehash @listeners_of = {} end def self.next_id - synchronize { - @id = (@id || 0) + 1 - } + @id = (@id || 0) + 1 end end diff --git a/lib/puppet/util/instrumentation/data.rb b/lib/puppet/util/instrumentation/data.rb index 9157f58fc..48e595432 100644 --- a/lib/puppet/util/instrumentation/data.rb +++ b/lib/puppet/util/instrumentation/data.rb @@ -20,12 +20,19 @@ class Puppet::Util::Instrumentation::Data @listener.name end - def to_pson(*args) - result = { + def to_data_hash + { :name => name }.merge(@listener.respond_to?(:data) ? @listener.data : {}) + end + + def to_pson_data_hash + { 'document_type' => "Puppet::Util::Instrumentation::Data", - 'data' => { :name => name }.merge(@listener.respond_to?(:data) ? @listener.data : {}) + 'data' => to_data_hash, } - result.to_pson(*args) + end + + def to_pson(*args) + to_pson_data_hash.to_pson(*args) end def self.from_pson(data) diff --git a/lib/puppet/util/instrumentation/indirection_probe.rb b/lib/puppet/util/instrumentation/indirection_probe.rb index ad9323a38..237d8dbc8 100644 --- a/lib/puppet/util/instrumentation/indirection_probe.rb +++ b/lib/puppet/util/instrumentation/indirection_probe.rb @@ -15,12 +15,19 @@ class Puppet::Util::Instrumentation::IndirectionProbe @probe_name = probe_name end - def to_pson(*args) - result = { + def to_data_hash + { :name => probe_name } + end + + def to_pson_data_hash + { :document_type => "Puppet::Util::Instrumentation::IndirectionProbe", - :data => { :name => probe_name } + :data => to_data_hash, } - result.to_pson(*args) + end + + def to_pson(*args) + to_pson_data_hash.to_pson(*args) end def self.from_pson(data) diff --git a/lib/puppet/util/instrumentation/instrumentable.rb b/lib/puppet/util/instrumentation/instrumentable.rb index 4c6d2f97e..01c75ff77 100644 --- a/lib/puppet/util/instrumentation/instrumentable.rb +++ b/lib/puppet/util/instrumentation/instrumentable.rb @@ -1,4 +1,3 @@ -require 'monitor' require 'puppet/util/instrumentation' # This is the central point of all declared probes. @@ -15,7 +14,7 @@ require 'puppet/util/instrumentation' # end # end module Puppet::Util::Instrumentation::Instrumentable - INSTRUMENTED_CLASSES = {}.extend(MonitorMixin) + INSTRUMENTED_CLASSES = {} attr_reader :probes @@ -101,10 +100,8 @@ module Puppet::Util::Instrumentation::Instrumentable # end # def probe(method, options = {}) - INSTRUMENTED_CLASSES.synchronize { - (@probes ||= []) << Probe.new(method, self, options) - INSTRUMENTED_CLASSES[self] = @probes - } + (@probes ||= []) << Probe.new(method, self, options) + INSTRUMENTED_CLASSES[self] = @probes end def self.probes @@ -126,18 +123,14 @@ module Puppet::Util::Instrumentation::Instrumentable end def self.clear_probes - INSTRUMENTED_CLASSES.synchronize { - INSTRUMENTED_CLASSES.clear - } + INSTRUMENTED_CLASSES.clear nil # do not leak our probes to the exterior world end def self.each_probe - INSTRUMENTED_CLASSES.synchronize { - INSTRUMENTED_CLASSES.each_key do |klass| - klass.probes.each { |probe| yield probe } - end - } + INSTRUMENTED_CLASSES.each_key do |klass| + klass.probes.each { |probe| yield probe } + end nil # do not leak our probes to the exterior world end end diff --git a/lib/puppet/util/instrumentation/listener.rb b/lib/puppet/util/instrumentation/listener.rb index 42ec0c0e9..b965e976f 100644 --- a/lib/puppet/util/instrumentation/listener.rb +++ b/lib/puppet/util/instrumentation/listener.rb @@ -41,16 +41,23 @@ class Puppet::Util::Instrumentation::Listener { :data => @listener.data } end - def to_pson(*args) - result = { + def to_data_hash + { + :name => name, + :pattern => pattern, + :enabled => enabled? + } + end + + def to_pson_data_hash + { :document_type => "Puppet::Util::Instrumentation::Listener", - :data => { - :name => name, - :pattern => pattern, - :enabled => enabled? - } + :data => to_data_hash, } - result.to_pson(*args) + end + + def to_pson(*args) + to_pson_data_hash.to_pson(*args) end def self.from_pson(data) diff --git a/lib/puppet/util/instrumentation/listeners/log.rb b/lib/puppet/util/instrumentation/listeners/log.rb index fcff14621..72797d628 100644 --- a/lib/puppet/util/instrumentation/listeners/log.rb +++ b/lib/puppet/util/instrumentation/listeners/log.rb @@ -1,5 +1,3 @@ -require 'monitor' - # This is an example instrumentation listener that stores the last # 20 instrumented probe run time. Puppet::Util::Instrumentation.new_listener(:log) do @@ -9,21 +7,17 @@ Puppet::Util::Instrumentation.new_listener(:log) do attr_accessor :last_logs def initialize - @last_logs = {}.extend(MonitorMixin) + @last_logs = {} end def notify(label, event, data) return if event == :start log_line = "#{label} took #{data[:finished] - data[:started]}" - @last_logs.synchronize { - (@last_logs[label] ||= []) << log_line - @last_logs[label].shift if @last_logs[label].length > SIZE - } + (@last_logs[label] ||= []) << log_line + @last_logs[label].shift if @last_logs[label].length > SIZE end def data - @last_logs.synchronize { - @last_logs.dup - } + @last_logs.dup end end diff --git a/lib/puppet/util/instrumentation/listeners/performance.rb b/lib/puppet/util/instrumentation/listeners/performance.rb index 3ad51b7de..5bf3e2c5b 100644 --- a/lib/puppet/util/instrumentation/listeners/performance.rb +++ b/lib/puppet/util/instrumentation/listeners/performance.rb @@ -1,30 +1,24 @@ -require 'monitor' - Puppet::Util::Instrumentation.new_listener(:performance) do attr_reader :samples def initialize - @samples = {}.extend(MonitorMixin) + @samples = {} end def notify(label, event, data) return if event == :start duration = data[:finished] - data[:started] - samples.synchronize do - @samples[label] ||= { :count => 0, :max => 0, :min => nil, :sum => 0, :average => 0 } - @samples[label][:count] += 1 - @samples[label][:sum] += duration - @samples[label][:max] = [ @samples[label][:max], duration ].max - @samples[label][:min] = [ @samples[label][:min], duration ].reject { |val| val.nil? }.min - @samples[label][:average] = @samples[label][:sum] / @samples[label][:count] - end + @samples[label] ||= { :count => 0, :max => 0, :min => nil, :sum => 0, :average => 0 } + @samples[label][:count] += 1 + @samples[label][:sum] += duration + @samples[label][:max] = [ @samples[label][:max], duration ].max + @samples[label][:min] = [ @samples[label][:min], duration ].reject { |val| val.nil? }.min + @samples[label][:average] = @samples[label][:sum] / @samples[label][:count] end def data - samples.synchronize do - @samples.dup - end + @samples.dup end end diff --git a/lib/puppet/util/limits.rb b/lib/puppet/util/limits.rb new file mode 100644 index 000000000..e2f805b00 --- /dev/null +++ b/lib/puppet/util/limits.rb @@ -0,0 +1,12 @@ +require 'puppet/util' + +module Puppet::Util::Limits + # @api private + def setpriority(priority) + return unless priority + + Process.setpriority(0, Process.pid, priority) + rescue Errno::EACCES, NotImplementedError + Puppet.warning("Failed to set process priority to '#{priority}'") + end +end diff --git a/lib/puppet/util/lockfile.rb b/lib/puppet/util/lockfile.rb index 75bc97213..4f1ad5716 100644 --- a/lib/puppet/util/lockfile.rb +++ b/lib/puppet/util/lockfile.rb @@ -31,7 +31,7 @@ class Puppet::Util::Lockfile def unlock if locked? - File.unlink(@file_path) + Puppet::FileSystem::File.unlink(@file_path) true else false @@ -56,7 +56,7 @@ class Puppet::Util::Lockfile # being overridden by child classes. # @return [boolean] true if the file is locked, false if it is not. def file_locked?() - File.exists? @file_path + Puppet::FileSystem::File.exist? @file_path end private :file_locked? end diff --git a/lib/puppet/util/log.rb b/lib/puppet/util/log.rb index 3d34bba2c..808e0631d 100644 --- a/lib/puppet/util/log.rb +++ b/lib/puppet/util/log.rb @@ -1,5 +1,6 @@ require 'puppet/util/tagging' require 'puppet/util/classgen' +require 'puppet/network/format_support' # Pass feedback to the user. Log levels are modeled after syslog's, and it is # expected that that will be the most common log destination. Supports @@ -8,6 +9,7 @@ class Puppet::Util::Log include Puppet::Util extend Puppet::Util::ClassGen include Puppet::Util::Tagging + include Puppet::Network::FormatSupport @levels = [:debug,:info,:notice,:warning,:err,:alert,:emerg,:crit] @loglevel = 2 @@ -165,9 +167,7 @@ class Puppet::Util::Log queuemessage(msg) if @destinations.length == 0 @destinations.each do |name, dest| - threadlock(dest) do - dest.handle(msg) - end + dest.handle(msg) end end @@ -265,7 +265,7 @@ class Puppet::Util::Log @level = data['level'].intern @message = data['message'] @source = data['source'] - @tags = data['tags'] + @tags = Puppet::Util::TagSet.new(data['tags']) @time = data['time'] if @time.is_a? String @time = Time.parse(@time) @@ -274,7 +274,11 @@ class Puppet::Util::Log @line = data['line'] if data['line'] end - def to_pson + def to_hash + self.to_data_hash + end + + def to_data_hash { 'level' => @level, 'message' => @message, @@ -283,7 +287,11 @@ class Puppet::Util::Log 'time' => @time.iso8601(9), 'file' => @file, 'line' => @line, - }.to_pson + } + end + + def to_pson(*args) + to_data_hash.to_pson(*args) end def message=(msg) diff --git a/lib/puppet/util/log/destinations.rb b/lib/puppet/util/log/destinations.rb index e33cfd9a8..ecb19c66b 100644 --- a/lib/puppet/util/log/destinations.rb +++ b/lib/puppet/util/log/destinations.rb @@ -68,7 +68,7 @@ Puppet::Util::Log.newdesttype :file do # first make sure the directory exists # We can't just use 'Config.use' here, because they've # specified a "special" destination. - unless FileTest.exist?(File.dirname(path)) + unless Puppet::FileSystem::File.exist?(File.dirname(path)) FileUtils.mkdir_p(File.dirname(path), :mode => 0755) Puppet.info "Creating log directory #{File.dirname(path)}" end @@ -95,6 +95,28 @@ Puppet::Util::Log.newdesttype :file do end end +Puppet::Util::Log.newdesttype :logstash_event do + require 'time' + + def format(msg) + # logstash_event format is documented at + # https://logstash.jira.com/browse/LOGSTASH-675 + + data = {} + data = msg.to_hash + data['version'] = 1 + data['@timestamp'] = data['time'] + data.delete('time') + + data + end + + def handle(msg) + message = format(msg) + $stdout.puts message.to_pson + end +end + Puppet::Util::Log.newdesttype :console do require 'puppet/util/colors' include Puppet::Util::Colors diff --git a/lib/puppet/util/metric.rb b/lib/puppet/util/metric.rb index 49d4edfc3..f37bbcfea 100644 --- a/lib/puppet/util/metric.rb +++ b/lib/puppet/util/metric.rb @@ -1,8 +1,10 @@ # included so we can test object types require 'puppet' +require 'puppet/network/format_support' # A class for handling metrics. This is currently ridiculously hackish. class Puppet::Util::Metric + include Puppet::Network::FormatSupport attr_accessor :type, :name, :value, :label attr_writer :values @@ -15,12 +17,16 @@ class Puppet::Util::Metric metric end - def to_pson + def to_data_hash { 'name' => @name, 'label' => @label, 'values' => @values - }.to_pson + } + end + + def to_pson(*args) + to_data_hash.to_pson(*args) end # Return a specific value @@ -155,7 +161,7 @@ class Puppet::Util::Metric Puppet.warning "RRD library is missing; cannot store metrics" return end - self.create(time - 5) unless FileTest.exists?(self.path) + self.create(time - 5) unless Puppet::FileSystem::File.exist?(self.path) if Puppet.features.rrd_legacy? && ! Puppet.features.rrd? @rrd ||= RRDtool.new(self.path) diff --git a/lib/puppet/util/monkey_patches.rb b/lib/puppet/util/monkey_patches.rb index b593ae3d3..3f0e68e8a 100644 --- a/lib/puppet/util/monkey_patches.rb +++ b/lib/puppet/util/monkey_patches.rb @@ -105,6 +105,7 @@ class IO end def self.binread(name, length = nil, offset = 0) + Puppet.deprecation_warning("This is a monkey-patched implementation of IO.binread on ruby 1.8 and is deprecated. Read the file without this method as it will be removed in a future version.") File.open(name, 'rb') do |f| f.seek(offset) if offset > 0 f.read(length) @@ -194,8 +195,12 @@ if Puppet::Util::Platform.windows? def set_default_paths # This can be removed once openssl integrates with windows # cert store, see http://rt.openssl.org/Ticket/Display.html?id=2158 - Puppet::Util::Windows::RootCerts.instance.each do |x509| - add_cert(x509) + Puppet::Util::Windows::RootCerts.instance.to_a.uniq.each do |x509| + begin + add_cert(x509) + rescue OpenSSL::X509::StoreError => e + warn "Failed to add #{x509.subject.to_s}" + end end __original_set_default_paths diff --git a/lib/puppet/util/network_device/config.rb b/lib/puppet/util/network_device/config.rb index ef49dd393..fe355708a 100644 --- a/lib/puppet/util/network_device/config.rb +++ b/lib/puppet/util/network_device/config.rb @@ -15,7 +15,7 @@ class Puppet::Util::NetworkDevice::Config attr_reader :devices def exists? - FileTest.exists?(@file) + Puppet::FileSystem::File.exist?(@file) end def initialize diff --git a/lib/puppet/util/plugins.rb b/lib/puppet/util/plugins.rb index 0bea67d05..dde496d35 100644 --- a/lib/puppet/util/plugins.rb +++ b/lib/puppet/util/plugins.rb @@ -37,7 +37,7 @@ module Puppet def self.known Paths[Loaded.length...Paths.length].each { |path| file = File.join(path,'plugin_init.rb') - Loaded << (File.exist?(file) && new(file)) + Loaded << (Puppet::FileSystem::File.exist?(file) && new(file)) } Loaded.compact end diff --git a/lib/puppet/util/posix.rb b/lib/puppet/util/posix.rb index 2af0e4b91..2af0e4b91 100755..100644 --- a/lib/puppet/util/posix.rb +++ b/lib/puppet/util/posix.rb diff --git a/lib/puppet/util/profiler.rb b/lib/puppet/util/profiler.rb index 0c3a3768f..c8d1762f5 100644 --- a/lib/puppet/util/profiler.rb +++ b/lib/puppet/util/profiler.rb @@ -10,14 +10,19 @@ module Puppet::Util::Profiler NONE = Puppet::Util::Profiler::None.new + # Reset the profiling system to the original state + def self.clear + @profiler = nil + end + # @return This thread's configured profiler def self.current - Thread.current[:profiler] || NONE + @profiler || NONE end # @param profiler [#profile] A profiler for the current thread def self.current=(profiler) - Thread.current[:profiler] = profiler + @profiler = profiler end # @param message [String] A description of the profiled event diff --git a/lib/puppet/util/provider_features.rb b/lib/puppet/util/provider_features.rb index d557c0380..f8c0c0aa6 100644 --- a/lib/puppet/util/provider_features.rb +++ b/lib/puppet/util/provider_features.rb @@ -84,7 +84,7 @@ module Puppet::Util::ProviderFeatures names = @features.keys.sort { |a,b| a.to_s <=> b.to_s } names.each do |name| doc = @features[name].docs.gsub(/\n\s+/, " ") - str += "- *#{name}*: #{doc}\n" + str << "- *#{name}*: #{doc}\n" end if providers.length > 0 @@ -101,7 +101,7 @@ module Puppet::Util::ProviderFeatures end end end - str += doctable(headers, data) + str << doctable(headers, data) end str end diff --git a/lib/puppet/util/rdoc.rb b/lib/puppet/util/rdoc.rb index becfa4ba3..49784956b 100644 --- a/lib/puppet/util/rdoc.rb +++ b/lib/puppet/util/rdoc.rb @@ -5,44 +5,42 @@ module Puppet::Util::RDoc # launch a rdoc documenation process # with the files/dir passed in +files+ def rdoc(outputdir, files, charset = nil) - unless Puppet.features.rdoc1? - raise "the version of RDoc included in Ruby #{::RUBY_VERSION} is not supported" - end - - begin - Puppet[:ignoreimport] = true + Puppet[:ignoreimport] = true - # then rdoc - require 'rdoc/rdoc' - require 'rdoc/options' + # then rdoc + require 'rdoc/rdoc' + require 'rdoc/options' - # load our parser - require 'puppet/util/rdoc/parser' + # load our parser + require 'puppet/util/rdoc/parser' - r = RDoc::RDoc.new + r = RDoc::RDoc.new + if Puppet.features.rdoc1? RDoc::RDoc::GENERATORS["puppet"] = RDoc::RDoc::Generator.new( - "puppet/util/rdoc/generators/puppet_generator.rb", - :PuppetGenerator, - "puppet" - ) - - # specify our own format & where to output - options = [ "--fmt", "puppet", - "--quiet", - "--exclude", "/modules/[^/]*/files/.*$", - "--exclude", "/modules/[^/]*/templates/.*$", - "--op", outputdir ] + "puppet/util/rdoc/generators/puppet_generator.rb", + :PuppetGenerator, + "puppet" + ) + end - options << "--force-update" if Options::OptionList.options.any? { |o| o[0] == "--force-update" } - options += [ "--charset", charset] if charset - options += files + # specify our own format & where to output + options = [ "--fmt", "puppet", + "--quiet", + "--exclude", "/modules/[^/]*/spec/.*$", + "--exclude", "/modules/[^/]*/files/.*$", + "--exclude", "/modules/[^/]*/tests/.*$", + "--exclude", "/modules/[^/]*/templates/.*$", + "--op", outputdir ] - # launch the documentation process - r.document(options) - rescue RDoc::RDocError => e - raise Puppet::ParseError.new("RDoc error #{e}") + if !Puppet.features.rdoc1? || ::Options::OptionList.options.any? { |o| o[0] == "--force-update" } # Options is a root object in the rdoc1 namespace... + options << "--force-update" end + options += [ "--charset", charset] if charset + options += files + + # launch the documentation process + r.document(options) end # launch an output to console manifest doc diff --git a/lib/puppet/util/rdoc/code_objects.rb b/lib/puppet/util/rdoc/code_objects.rb index 3c789a0c5..7edd4134e 100644 --- a/lib/puppet/util/rdoc/code_objects.rb +++ b/lib/puppet/util/rdoc/code_objects.rb @@ -9,25 +9,20 @@ module RDoc # PuppetGenerator. # PuppetTopLevel is a top level (usually a .pp/.rb file) - class PuppetTopLevel < TopLevel + module PuppetTopLevel attr_accessor :module_name, :global + end - # will contain all plugins - @@all_plugins = {} - - # contains all cutoms facts - @@all_facts = {} - - def initialize(toplevel) - super(toplevel.file_relative_name) - end - - def self.all_plugins - @@all_plugins.values - end - - def self.all_facts - @@all_facts.values + # Add top level comments to a class or module regardless of whether we are + # using rdoc1 or rdoc2+ + # @api private + module AddClassModuleComment + def add_comment(comment, location = nil) + if PUPPET_RDOC_VERSION == 1 + self.comment = comment + else + super + end end end @@ -35,28 +30,67 @@ module RDoc # This is mapped to an HTMLPuppetModule # it leverage the RDoc (ruby) module infrastructure class PuppetModule < NormalModule + include AddClassModuleComment + attr_accessor :facts, :plugins def initialize(name,superclass=nil) @facts = [] @plugins = [] + @nodes = {} super(name,superclass) end - def initialize_classes_and_modules - super - @nodes = {} + def add_plugin(plugin) + if PUPPET_RDOC_VERSION == 1 + add_to(@plugins, plugin) + else + add_plugin_rdoc2(plugin) + end end - def add_plugin(plugin) - add_to(@plugins, plugin) + def add_plugin_rdoc2(plugin) + name = plugin.name + type = plugin.type + meth = AnyMethod.new("*args", name) + meth.params = "(*args)" + meth.visibility = :public + meth.document_self = true + meth.singleton = false + meth.comment = plugin.comment + if type == 'function' + @function_container ||= add_module(NormalModule, "__functions__") + @function_container.add_method(meth) + elsif type == 'type' + @type_container ||= add_module(NormalModule, "__types__") + @type_container.add_method(meth) + end end def add_fact(fact) - add_to(@facts, fact) + if PUPPET_RDOC_VERSION == 1 + add_to(@facts, fact) + else + add_fact_rdoc2(fact) + end + end + + def add_fact_rdoc2(fact) + @fact_container ||= add_module(NormalModule, "__facts__") + confine_str = fact.confine.empty? ? '' : fact.confine.to_s + const = Constant.new(fact.name, confine_str, fact.comment) + @fact_container.add_constant(const) + end + + def add_node(name, superclass) + if PUPPET_RDOC_VERSION == 1 + add_node_rdoc1(name, superclass) + else + add_node_rdoc2(name, superclass) + end end - def add_node(name,superclass) + def add_node_rdoc1(name, superclass) cls = @nodes[name] unless cls cls = PuppetNode.new(name, superclass) @@ -67,6 +101,18 @@ module RDoc cls end + # Adds a module called __nodes__ and adds nodes to it as classes + # + def add_node_rdoc2(name,superclass) + if cls = @nodes[name] + return cls + end + @node_container ||= add_module(NormalModule, "__nodes__") + cls = @node_container.add_class(PuppetNode, name, superclass) + @nodes[name] = cls if !@done_documenting + cls + end + def each_fact @facts.each {|c| yield c} end @@ -88,6 +134,8 @@ module RDoc # It is mapped to a HTMLPuppetClass for display # It leverages RDoc (ruby) Class class PuppetClass < ClassModule + include AddClassModuleComment + attr_accessor :resource_list, :requires, :childs, :realizes def initialize(name, superclass) @@ -130,7 +178,7 @@ module RDoc # but are written class1::class2::define we need to perform the lookup by # ourselves. def find_symbol(symbol, method=nil) - result = super + result = super(symbol) if not result and symbol =~ /::/ modules = symbol.split(/::/) unless modules.empty? @@ -169,6 +217,8 @@ module RDoc # It is mapped to a HTMLPuppetNode for display # A node is just a variation of a class class PuppetNode < PuppetClass + include AddClassModuleComment + def initialize(name, superclass) super(name,superclass) end diff --git a/lib/puppet/util/rdoc/generators/puppet_generator.rb b/lib/puppet/util/rdoc/generators/puppet_generator.rb index 5c6aca28e..142124769 100644 --- a/lib/puppet/util/rdoc/generators/puppet_generator.rb +++ b/lib/puppet/util/rdoc/generators/puppet_generator.rb @@ -246,7 +246,7 @@ module Generators end def gen_composite_index(collection, template, filename)\ - return if FileTest.exists?(filename) + return if Puppet::FileSystem::File.exist?(filename) template = TemplatePage.new(RDoc::Page::FR_INDEX_BODY, template) res1 = [] diff --git a/lib/puppet/util/rdoc/parser.rb b/lib/puppet/util/rdoc/parser.rb index 6b5ad740b..6ecd4921c 100644 --- a/lib/puppet/util/rdoc/parser.rb +++ b/lib/puppet/util/rdoc/parser.rb @@ -6,492 +6,17 @@ # rdoc mandatory includes require "rdoc/code_objects" require "puppet/util/rdoc/code_objects" -require "rdoc/tokenstream" -if ::RUBY_VERSION =~ /^1.8/ - require "rdoc/markup/simple_markup/preprocess" - require "rdoc/parsers/parserfactory" -else - require "rdoc/markup/preprocess" - require "rdoc/parser" -end - -module RDoc - -class Parser - extend ParserFactory if ::RUBY_VERSION =~ /^1.8/ - - SITE = "__site__" - - attr_accessor :input_file_name, :top_level - - # parser registration into RDoc - parse_files_matching(/\.(rb|pp)$/) - - # called with the top level file - def initialize(top_level, file_name, content, options, stats) - @options = options - @stats = stats - @input_file_name = file_name - @top_level = PuppetTopLevel.new(top_level) - @progress = $stderr unless options.quiet - end - - # main entry point - def scan - environment = Puppet::Node::Environment.new - @known_resource_types = environment.known_resource_types - unless environment.known_resource_types.watching_file?(@input_file_name) - Puppet.info "rdoc: scanning #{@input_file_name}" - if @input_file_name =~ /\.pp$/ - @parser = Puppet::Parser::Parser.new(environment) - @parser.file = @input_file_name - @parser.parse.instantiate('').each do |type| - @known_resource_types.add type - end - end - end - - scan_top_level(@top_level) - @top_level - end - - # Due to a bug in RDoc, we need to roll our own find_module_named - # The issue is that RDoc tries harder by asking the parent for a class/module - # of the name. But by doing so, it can mistakenly use a module of same name - # but from which we are not descendant. - def find_object_named(container, name) - return container if container.name == name - container.each_classmodule do |m| - return m if m.name == name - end - nil - end - - # walk down the namespace and lookup/create container as needed - def get_class_or_module(container, name) - - # class ::A -> A is in the top level - if name =~ /^::/ - container = @top_level - end - - names = name.split('::') - - final_name = names.pop - names.each do |name| - prev_container = container - container = find_object_named(container, name) - container ||= prev_container.add_class(PuppetClass, name, nil) - end - [container, final_name] - end - - # split_module tries to find if +path+ belongs to the module path - # if it does, it returns the module name, otherwise if we are sure - # it is part of the global manifest path, "__site__" is returned. - # And finally if this path couldn't be mapped anywhere, nil is returned. - def split_module(path) - # find a module - fullpath = File.expand_path(path) - Puppet.debug "rdoc: testing #{fullpath}" - if fullpath =~ /(.*)\/([^\/]+)\/(?:manifests|plugins|lib)\/.+\.(pp|rb)$/ - modpath = $1 - name = $2 - Puppet.debug "rdoc: module #{name} into #{modpath} ?" - Puppet::Node::Environment.new.modulepath.each do |mp| - if File.identical?(modpath,mp) - Puppet.debug "rdoc: found module #{name}" - return name - end - end - end - if fullpath =~ /\.(pp|rb)$/ - # there can be paths we don't want to scan under modules - # imagine a ruby or manifest that would be distributed as part as a module - # but we don't want those to be hosted under <site> - Puppet::Node::Environment.new.modulepath.each do |mp| - # check that fullpath is a descendant of mp - dirname = fullpath - previous = dirname - while (dirname = File.dirname(previous)) != previous - previous = dirname - return nil if File.identical?(dirname,mp) - end - end - end - # we are under a global manifests - Puppet.debug "rdoc: global manifests" - SITE - end - - # create documentation for the top level +container+ - def scan_top_level(container) - # use the module README as documentation for the module - comment = "" - %w{README README.rdoc}.each do |rfile| - readme = File.join(File.dirname(File.dirname(@input_file_name)), rfile) - comment = File.open(readme,"r") { |f| f.read } if FileTest.readable?(readme) - end - look_for_directives_in(container, comment) unless comment.empty? - - # infer module name from directory - name = split_module(@input_file_name) - if name.nil? - # skip .pp files that are not in manifests directories as we can't guarantee they're part - # of a module or the global configuration. - container.document_self = false - return - end - - Puppet.debug "rdoc: scanning for #{name}" - - container.module_name = name - container.global=true if name == SITE - - @stats.num_modules += 1 - container, name = get_class_or_module(container,name) - mod = container.add_module(PuppetModule, name) - mod.record_location(@top_level) - mod.comment = comment - - if @input_file_name =~ /\.pp$/ - parse_elements(mod) - elsif @input_file_name =~ /\.rb$/ - parse_plugins(mod) - end - end - - # create documentation for include statements we can find in +code+ - # and associate it with +container+ - def scan_for_include_or_require(container, code) - code = [code] unless code.is_a?(Array) - code.each do |stmt| - scan_for_include_or_require(container,stmt.children) if stmt.is_a?(Puppet::Parser::AST::ASTArray) - - if stmt.is_a?(Puppet::Parser::AST::Function) and ['include','require'].include?(stmt.name) - stmt.arguments.each do |included| - Puppet.debug "found #{stmt.name}: #{included}" - container.send("add_#{stmt.name}",Include.new(included.to_s, stmt.doc)) - end - end - end - end - - # create documentation for realize statements we can find in +code+ - # and associate it with +container+ - def scan_for_realize(container, code) - code = [code] unless code.is_a?(Array) - code.each do |stmt| - scan_for_realize(container,stmt.children) if stmt.is_a?(Puppet::Parser::AST::ASTArray) - - if stmt.is_a?(Puppet::Parser::AST::Function) and stmt.name == 'realize' - stmt.arguments.each do |realized| - Puppet.debug "found #{stmt.name}: #{realized}" - container.add_realize(Include.new(realized.to_s, stmt.doc)) - end - end - end - end - - # create documentation for global variables assignements we can find in +code+ - # and associate it with +container+ - def scan_for_vardef(container, code) - code = [code] unless code.is_a?(Array) - code.each do |stmt| - scan_for_vardef(container,stmt.children) if stmt.is_a?(Puppet::Parser::AST::ASTArray) - - if stmt.is_a?(Puppet::Parser::AST::VarDef) - Puppet.debug "rdoc: found constant: #{stmt.name} = #{stmt.value}" - container.add_constant(Constant.new(stmt.name.to_s, stmt.value.to_s, stmt.doc)) - end - end - end - - # create documentation for resources we can find in +code+ - # and associate it with +container+ - def scan_for_resource(container, code) - code = [code] unless code.is_a?(Array) - code.each do |stmt| - scan_for_resource(container,stmt.children) if stmt.is_a?(Puppet::Parser::AST::ASTArray) - - if stmt.is_a?(Puppet::Parser::AST::Resource) and !stmt.type.nil? - begin - type = stmt.type.split("::").collect { |s| s.capitalize }.join("::") - stmt.instances.each do |inst| - title = inst.title.is_a?(Puppet::Parser::AST::ASTArray) ? inst.title.to_s.gsub(/\[(.*)\]/,'\1') : inst.title.to_s - Puppet.debug "rdoc: found resource: #{type}[#{title}]" - - param = [] - inst.parameters.children.each do |p| - res = {} - res["name"] = p.param - res["value"] = "#{p.value.to_s}" unless p.value.nil? - - param << res - end - - container.add_resource(PuppetResource.new(type, title, stmt.doc, param)) - end - rescue => detail - raise Puppet::ParseError, "impossible to parse resource in #{stmt.file} at line #{stmt.line}: #{detail}" - end - end - end - end - - def resource_stmt_to_ref(stmt) - type = stmt.type.split("::").collect { |s| s.capitalize }.join("::") - title = stmt.title.is_a?(Puppet::Parser::AST::ASTArray) ? stmt.title.to_s.gsub(/\[(.*)\]/,'\1') : stmt.title.to_s - - param = stmt.params.children.collect do |p| - {"name" => p.param, "value" => p.value.to_s} - end - PuppetResource.new(type, title, stmt.doc, param) - end - - # create documentation for a class named +name+ - def document_class(name, klass, container) - Puppet.debug "rdoc: found new class #{name}" - container, name = get_class_or_module(container, name) - - superclass = klass.parent - superclass = "" if superclass.nil? or superclass.empty? - - @stats.num_classes += 1 - comment = klass.doc - look_for_directives_in(container, comment) unless comment.empty? - cls = container.add_class(PuppetClass, name, superclass) - # it is possible we already encountered this class, while parsing some namespaces - # from other classes of other files. But at that time we couldn't know this class superclass - # so, now we know it and force it. - cls.superclass = superclass - cls.record_location(@top_level) - - # scan class code for include - code = klass.code.children if klass.code.is_a?(Puppet::Parser::AST::ASTArray) - code ||= klass.code - unless code.nil? - scan_for_include_or_require(cls, code) - scan_for_realize(cls, code) - scan_for_resource(cls, code) if Puppet.settings[:document_all] - end - - cls.comment = comment - rescue => detail - raise Puppet::ParseError, "impossible to parse class '#{name}' in #{klass.file} at line #{klass.line}: #{detail}" - end - - # create documentation for a node - def document_node(name, node, container) - Puppet.debug "rdoc: found new node #{name}" - superclass = node.parent - superclass = "" if superclass.nil? or superclass.empty? - - comment = node.doc - look_for_directives_in(container, comment) unless comment.empty? - n = container.add_node(name, superclass) - n.record_location(@top_level) - - code = node.code.children if node.code.is_a?(Puppet::Parser::AST::ASTArray) - code ||= node.code - unless code.nil? - scan_for_include_or_require(n, code) - scan_for_realize(n, code) - scan_for_vardef(n, code) - scan_for_resource(n, code) if Puppet.settings[:document_all] - end - - n.comment = comment - rescue => detail - raise Puppet::ParseError, "impossible to parse node '#{name}' in #{node.file} at line #{node.line}: #{detail}" - end - - # create documentation for a define - def document_define(name, define, container) - Puppet.debug "rdoc: found new definition #{name}" - # find superclas if any - @stats.num_methods += 1 - - # find the parent - # split define name by :: to find the complete module hierarchy - container, name = get_class_or_module(container,name) - - # build up declaration - declaration = "" - define.arguments.each do |arg,value| - declaration << "\$#{arg}" - unless value.nil? - declaration << " => " - case value - when Puppet::Parser::AST::Leaf - declaration << "'#{value.value}'" - when Puppet::Parser::AST::ASTArray - declaration << "[#{value.children.collect { |v| "'#{v}'" }.join(", ")}]" - else - declaration << "#{value.to_s}" - end - end - declaration << ", " - end - declaration.chop!.chop! if declaration.size > 1 - - # register method into the container - meth = AnyMethod.new(declaration, name) - meth.comment = define.doc - container.add_method(meth) - look_for_directives_in(container, meth.comment) unless meth.comment.empty? - meth.params = "( #{declaration} )" - meth.visibility = :public - meth.document_self = true - meth.singleton = false - rescue => detail - raise Puppet::ParseError, "impossible to parse definition '#{name}' in #{define.file} at line #{define.line}: #{detail}" - end - - # Traverse the AST tree and produce code-objects node - # that contains the documentation - def parse_elements(container) - Puppet.debug "rdoc: scanning manifest" - - @known_resource_types.hostclasses.values.sort { |a,b| a.name <=> b.name }.each do |klass| - name = klass.name - if klass.file == @input_file_name - unless name.empty? - document_class(name,klass,container) - else # on main class document vardefs - code = klass.code.children if klass.code.is_a?(Puppet::Parser::AST::ASTArray) - code ||= klass.code - scan_for_vardef(container, code) unless code.nil? - end - end - end - - @known_resource_types.definitions.each do |name, define| - if define.file == @input_file_name - document_define(name,define,container) - end - end - - @known_resource_types.nodes.each do |name, node| - if node.file == @input_file_name - document_node(name.to_s,node,container) - end - end - end - - # create documentation for plugins - def parse_plugins(container) - Puppet.debug "rdoc: scanning plugin or fact" - if @input_file_name =~ /\/facter\/[^\/]+\.rb$/ - parse_fact(container) - else - parse_puppet_plugin(container) - end - end - - # this is a poor man custom fact parser :-) - def parse_fact(container) - comments = "" - current_fact = nil - File.open(@input_file_name) do |of| - of.each do |line| - # fetch comments - if line =~ /^[ \t]*# ?(.*)$/ - comments += $1 + "\n" - elsif line =~ /^[ \t]*Facter.add\(['"](.*?)['"]\)/ - current_fact = Fact.new($1,{}) - look_for_directives_in(container, comments) unless comments.empty? - current_fact.comment = comments - container.add_fact(current_fact) - current_fact.record_location(@top_level) - comments = "" - Puppet.debug "rdoc: found custom fact #{current_fact.name}" - elsif line =~ /^[ \t]*confine[ \t]*:(.*?)[ \t]*=>[ \t]*(.*)$/ - current_fact.confine = { :type => $1, :value => $2 } unless current_fact.nil? - else # unknown line type - comments ="" - end - end - end - end - - # this is a poor man puppet plugin parser :-) - # it doesn't extract doc nor desc :-( - def parse_puppet_plugin(container) - comments = "" - current_plugin = nil - - File.open(@input_file_name) do |of| - of.each do |line| - # fetch comments - if line =~ /^[ \t]*# ?(.*)$/ - comments += $1 + "\n" - elsif line =~ /^[ \t]*newfunction[ \t]*\([ \t]*:(.*?)[ \t]*,[ \t]*:type[ \t]*=>[ \t]*(:rvalue|:lvalue)\)/ - current_plugin = Plugin.new($1, "function") - container.add_plugin(current_plugin) - look_for_directives_in(container, comments) unless comments.empty? - current_plugin.comment = comments - current_plugin.record_location(@top_level) - comments = "" - Puppet.debug "rdoc: found new function plugins #{current_plugin.name}" - elsif line =~ /^[ \t]*Puppet::Type.newtype[ \t]*\([ \t]*:(.*?)\)/ - current_plugin = Plugin.new($1, "type") - container.add_plugin(current_plugin) - look_for_directives_in(container, comments) unless comments.empty? - current_plugin.comment = comments - current_plugin.record_location(@top_level) - comments = "" - Puppet.debug "rdoc: found new type plugins #{current_plugin.name}" - elsif line =~ /module Puppet::Parser::Functions/ - # skip - else # unknown line type - comments ="" - end - end - end - end - - # look_for_directives_in scans the current +comment+ for RDoc directives - def look_for_directives_in(context, comment) - preprocess = SM::PreProcess.new(@input_file_name, @options.rdoc_include) - - preprocess.handle(comment) do |directive, param| - case directive - when "stopdoc" - context.stop_doc - "" - when "startdoc" - context.start_doc - context.force_documentation = true - "" - when "enddoc" - #context.done_documenting = true - #"" - throw :enddoc - when "main" - options = Options.instance - options.main_page = param - "" - when "title" - options = Options.instance - options.title = param - "" - when "section" - context.set_current_section(param, comment) - comment.replace("") # 1.8 doesn't support #clear - break - else - warn "Unrecognized directive '#{directive}'" - break - end - end - remove_private_comments(comment) - end - - def remove_private_comments(comment) - comment.gsub!(/^#--.*?^#\+\+/m, '') - comment.sub!(/^#--.*/m, '') - end -end +begin + # Rdoc 1 imports + require "rdoc/tokenstream" + require "rdoc/markup/simple_markup/preprocess" + require "rdoc/parsers/parserfactory" + require "puppet/util/rdoc/parser/puppet_parser_rdoc1.rb" +rescue LoadError + # Current version imports + require "rdoc/token_stream" + require "rdoc/markup/pre_process" + require "rdoc/parser" + require "puppet/util/rdoc/parser/puppet_parser_rdoc2.rb" end diff --git a/lib/puppet/util/rdoc/parser/puppet_parser_core.rb b/lib/puppet/util/rdoc/parser/puppet_parser_core.rb new file mode 100644 index 000000000..dd7d03caf --- /dev/null +++ b/lib/puppet/util/rdoc/parser/puppet_parser_core.rb @@ -0,0 +1,477 @@ +# Functionality common to both our RDoc version 1 and 2 parsers. +module RDoc::PuppetParserCore + + SITE = "__site__" + + def self.included(base) + base.class_eval do + attr_accessor :input_file_name, :top_level + + # parser registration into RDoc + parse_files_matching(/\.(rb|pp)$/) + end + end + + # called with the top level file + def initialize(top_level, file_name, body, options, stats) + @options = options + @stats = stats + @input_file_name = file_name + @top_level = top_level + @top_level.extend(RDoc::PuppetTopLevel) + @progress = $stderr unless options.quiet + end + + # main entry point + def scan + environment = Puppet::Node::Environment.new + @known_resource_types = environment.known_resource_types + unless environment.known_resource_types.watching_file?(@input_file_name) + Puppet.info "rdoc: scanning #{@input_file_name}" + if @input_file_name =~ /\.pp$/ + @parser = Puppet::Parser::Parser.new(environment) + @parser.file = @input_file_name + @parser.parse.instantiate('').each do |type| + @known_resource_types.add type + end + end + end + + scan_top_level(@top_level) + @top_level + end + + # Due to a bug in RDoc, we need to roll our own find_module_named + # The issue is that RDoc tries harder by asking the parent for a class/module + # of the name. But by doing so, it can mistakenly use a module of same name + # but from which we are not descendant. + def find_object_named(container, name) + return container if container.name == name + container.each_classmodule do |m| + return m if m.name == name + end + nil + end + + # walk down the namespace and lookup/create container as needed + def get_class_or_module(container, name) + + # class ::A -> A is in the top level + if name =~ /^::/ + container = @top_level + end + + names = name.split('::') + + final_name = names.pop + names.each do |name| + prev_container = container + container = find_object_named(container, name) + container ||= prev_container.add_class(RDoc::PuppetClass, name, nil) + end + [container, final_name] + end + + # split_module tries to find if +path+ belongs to the module path + # if it does, it returns the module name, otherwise if we are sure + # it is part of the global manifest path, "__site__" is returned. + # And finally if this path couldn't be mapped anywhere, nil is returned. + def split_module(path) + # find a module + fullpath = File.expand_path(path) + Puppet.debug "rdoc: testing #{fullpath}" + if fullpath =~ /(.*)\/([^\/]+)\/(?:manifests|plugins|lib)\/.+\.(pp|rb)$/ + modpath = $1 + name = $2 + Puppet.debug "rdoc: module #{name} into #{modpath} ?" + Puppet::Node::Environment.new.modulepath.each do |mp| + if File.identical?(modpath,mp) + Puppet.debug "rdoc: found module #{name}" + return name + end + end + end + if fullpath =~ /\.(pp|rb)$/ + # there can be paths we don't want to scan under modules + # imagine a ruby or manifest that would be distributed as part as a module + # but we don't want those to be hosted under <site> + Puppet::Node::Environment.new.modulepath.each do |mp| + # check that fullpath is a descendant of mp + dirname = fullpath + previous = dirname + while (dirname = File.dirname(previous)) != previous + previous = dirname + return nil if File.identical?(dirname,mp) + end + end + end + # we are under a global manifests + Puppet.debug "rdoc: global manifests" + SITE + end + + # create documentation for the top level +container+ + def scan_top_level(container) + # use the module README as documentation for the module + comment = "" + %w{README README.rdoc}.each do |rfile| + readme = File.join(File.dirname(File.dirname(@input_file_name)), rfile) + comment = File.open(readme,"r") { |f| f.read } if FileTest.readable?(readme) + end + look_for_directives_in(container, comment) unless comment.empty? + + # infer module name from directory + name = split_module(@input_file_name) + if name.nil? + # skip .pp files that are not in manifests directories as we can't guarantee they're part + # of a module or the global configuration. + container.document_self = false + return + end + + Puppet.debug "rdoc: scanning for #{name}" + + container.module_name = name + container.global=true if name == SITE + + container, name = get_class_or_module(container,name) + mod = container.add_module(RDoc::PuppetModule, name) + mod.record_location(@top_level) + mod.add_comment(comment, @input_file_name) + + if @input_file_name =~ /\.pp$/ + parse_elements(mod) + elsif @input_file_name =~ /\.rb$/ + parse_plugins(mod) + end + end + + # create documentation for include statements we can find in +code+ + # and associate it with +container+ + def scan_for_include_or_require(container, code) + code = [code] unless code.is_a?(Array) + code.each do |stmt| + scan_for_include_or_require(container,stmt.children) if stmt.is_a?(Puppet::Parser::AST::BlockExpression) + + if stmt.is_a?(Puppet::Parser::AST::Function) and ['include','require'].include?(stmt.name) + stmt.arguments.each do |included| + Puppet.debug "found #{stmt.name}: #{included}" + container.send("add_#{stmt.name}", RDoc::Include.new(included.to_s, stmt.doc)) + end + end + end + end + + # create documentation for realize statements we can find in +code+ + # and associate it with +container+ + def scan_for_realize(container, code) + code = [code] unless code.is_a?(Array) + code.each do |stmt| + scan_for_realize(container,stmt.children) if stmt.is_a?(Puppet::Parser::AST::BlockExpression) + + if stmt.is_a?(Puppet::Parser::AST::Function) and stmt.name == 'realize' + stmt.arguments.each do |realized| + Puppet.debug "found #{stmt.name}: #{realized}" + container.add_realize( RDoc::Include.new(realized.to_s, stmt.doc)) + end + end + end + end + + # create documentation for global variables assignements we can find in +code+ + # and associate it with +container+ + def scan_for_vardef(container, code) + code = [code] unless code.is_a?(Array) + code.each do |stmt| + scan_for_vardef(container,stmt.children) if stmt.is_a?(Puppet::Parser::AST::BlockExpression) + + if stmt.is_a?(Puppet::Parser::AST::VarDef) + Puppet.debug "rdoc: found constant: #{stmt.name} = #{stmt.value}" + container.add_constant(RDoc::Constant.new(stmt.name.to_s, stmt.value.to_s, stmt.doc)) + end + end + end + + # create documentation for resources we can find in +code+ + # and associate it with +container+ + def scan_for_resource(container, code) + code = [code] unless code.is_a?(Array) + code.each do |stmt| + scan_for_resource(container,stmt.children) if stmt.is_a?(Puppet::Parser::AST::BlockExpression) + + if stmt.is_a?(Puppet::Parser::AST::Resource) and !stmt.type.nil? + begin + type = stmt.type.split("::").collect { |s| s.capitalize }.join("::") + stmt.instances.each do |inst| + title = inst.title.is_a?(Puppet::Parser::AST::ASTArray) ? inst.title.to_s.gsub(/\[(.*)\]/,'\1') : inst.title.to_s + Puppet.debug "rdoc: found resource: #{type}[#{title}]" + + param = [] + inst.parameters.children.each do |p| + res = {} + res["name"] = p.param + res["value"] = "#{p.value.to_s}" unless p.value.nil? + + param << res + end + + container.add_resource(RDoc::PuppetResource.new(type, title, stmt.doc, param)) + end + rescue => detail + raise Puppet::ParseError, "impossible to parse resource in #{stmt.file} at line #{stmt.line}: #{detail}" + end + end + end + end + + # create documentation for a class named +name+ + def document_class(name, klass, container) + Puppet.debug "rdoc: found new class #{name}" + container, name = get_class_or_module(container, name) + + superclass = klass.parent + superclass = "" if superclass.nil? or superclass.empty? + + comment = klass.doc + look_for_directives_in(container, comment) unless comment.empty? + cls = container.add_class(RDoc::PuppetClass, name, superclass) + # it is possible we already encountered this class, while parsing some namespaces + # from other classes of other files. But at that time we couldn't know this class superclass + # so, now we know it and force it. + cls.superclass = superclass + cls.record_location(@top_level) + + # scan class code for include + code = klass.code.children if klass.code.is_a?(Puppet::Parser::AST::BlockExpression) + code ||= klass.code + unless code.nil? + scan_for_include_or_require(cls, code) + scan_for_realize(cls, code) + scan_for_resource(cls, code) if Puppet.settings[:document_all] + end + + cls.add_comment(comment, klass.file) + rescue => detail + raise Puppet::ParseError, "impossible to parse class '#{name}' in #{klass.file} at line #{klass.line}: #{detail}" + end + + # create documentation for a node + def document_node(name, node, container) + Puppet.debug "rdoc: found new node #{name}" + superclass = node.parent + superclass = "" if superclass.nil? or superclass.empty? + + comment = node.doc + look_for_directives_in(container, comment) unless comment.empty? + n = container.add_node(name, superclass) + n.record_location(@top_level) + + code = node.code.children if node.code.is_a?(Puppet::Parser::AST::BlockExpression) + code ||= node.code + unless code.nil? + scan_for_include_or_require(n, code) + scan_for_realize(n, code) + scan_for_vardef(n, code) + scan_for_resource(n, code) if Puppet.settings[:document_all] + end + + n.add_comment(comment, node.file) + rescue => detail + raise Puppet::ParseError, "impossible to parse node '#{name}' in #{node.file} at line #{node.line}: #{detail}" + end + + # create documentation for a define + def document_define(name, define, container) + Puppet.debug "rdoc: found new definition #{name}" + # find superclas if any + + # find the parent + # split define name by :: to find the complete module hierarchy + container, name = get_class_or_module(container,name) + + # build up declaration + declaration = "" + define.arguments.each do |arg,value| + declaration << "\$#{arg}" + unless value.nil? + declaration << " => " + case value + when Puppet::Parser::AST::Leaf + declaration << "'#{value.value}'" + when Puppet::Parser::AST::BlockExpression + declaration << "[#{value.children.collect { |v| "'#{v}'" }.join(", ")}]" + else + declaration << "#{value.to_s}" + end + end + declaration << ", " + end + declaration.chop!.chop! if declaration.size > 1 + + # register method into the container + meth = RDoc::AnyMethod.new(declaration, name) + meth.comment = define.doc + container.add_method(meth) + look_for_directives_in(container, meth.comment) unless meth.comment.empty? + meth.params = "( #{declaration} )" + meth.visibility = :public + meth.document_self = true + meth.singleton = false + rescue => detail + raise Puppet::ParseError, "impossible to parse definition '#{name}' in #{define.file} at line #{define.line}: #{detail}" + end + + # Traverse the AST tree and produce code-objects node + # that contains the documentation + def parse_elements(container) + Puppet.debug "rdoc: scanning manifest" + + @known_resource_types.hostclasses.values.sort { |a,b| a.name <=> b.name }.each do |klass| + name = klass.name + if klass.file == @input_file_name + unless name.empty? + document_class(name,klass,container) + else # on main class document vardefs + code = klass.code.children if klass.code.is_a?(Puppet::Parser::AST::BlockExpression) + code ||= klass.code + scan_for_vardef(container, code) unless code.nil? + end + end + end + + @known_resource_types.definitions.each do |name, define| + if define.file == @input_file_name + document_define(name,define,container) + end + end + + @known_resource_types.nodes.each do |name, node| + if node.file == @input_file_name + document_node(name.to_s,node,container) + end + end + end + + # create documentation for plugins + def parse_plugins(container) + Puppet.debug "rdoc: scanning plugin or fact" + if @input_file_name =~ /\/facter\/[^\/]+\.rb$/ + parse_fact(container) + else + parse_puppet_plugin(container) + end + end + + # this is a poor man custom fact parser :-) + def parse_fact(container) + comments = "" + current_fact = nil + parsed_facts = [] + File.open(@input_file_name) do |of| + of.each do |line| + # fetch comments + if line =~ /^[ \t]*# ?(.*)$/ + comments += $1 + "\n" + elsif line =~ /^[ \t]*Facter.add\(['"](.*?)['"]\)/ + current_fact = RDoc::Fact.new($1,{}) + look_for_directives_in(container, comments) unless comments.empty? + current_fact.comment = comments + parsed_facts << current_fact + comments = "" + Puppet.debug "rdoc: found custom fact #{current_fact.name}" + elsif line =~ /^[ \t]*confine[ \t]*:(.*?)[ \t]*=>[ \t]*(.*)$/ + current_fact.confine = { :type => $1, :value => $2 } unless current_fact.nil? + else # unknown line type + comments ="" + end + end + end + parsed_facts.each do |f| + container.add_fact(f) + f.record_location(@top_level) + end + end + + # this is a poor man puppet plugin parser :-) + # it doesn't extract doc nor desc :-( + def parse_puppet_plugin(container) + comments = "" + current_plugin = nil + + File.open(@input_file_name) do |of| + of.each do |line| + # fetch comments + if line =~ /^[ \t]*# ?(.*)$/ + comments += $1 + "\n" + elsif line =~ /^[ \t]*(?:Puppet::Parser::Functions::)?newfunction[ \t]*\([ \t]*:(.*?)[ \t]*,[ \t]*:type[ \t]*=>[ \t]*(:rvalue|:lvalue)/ + current_plugin = RDoc::Plugin.new($1, "function") + look_for_directives_in(container, comments) unless comments.empty? + current_plugin.comment = comments + current_plugin.record_location(@top_level) + container.add_plugin(current_plugin) + comments = "" + Puppet.debug "rdoc: found new function plugins #{current_plugin.name}" + elsif line =~ /^[ \t]*Puppet::Type.newtype[ \t]*\([ \t]*:(.*?)\)/ + current_plugin = RDoc::Plugin.new($1, "type") + look_for_directives_in(container, comments) unless comments.empty? + current_plugin.comment = comments + current_plugin.record_location(@top_level) + container.add_plugin(current_plugin) + comments = "" + Puppet.debug "rdoc: found new type plugins #{current_plugin.name}" + elsif line =~ /module Puppet::Parser::Functions/ + # skip + else # unknown line type + comments ="" + end + end + end + end + + # New instance of the appropriate PreProcess for our RDoc version. + def create_rdoc_preprocess + raise(NotImplementedError, "This method must be overwritten for whichever version of RDoc this parser is working with") + end + + # look_for_directives_in scans the current +comment+ for RDoc directives + def look_for_directives_in(context, comment) + preprocess = create_rdoc_preprocess + + preprocess.handle(comment) do |directive, param| + case directive + when "stopdoc" + context.stop_doc + "" + when "startdoc" + context.start_doc + context.force_documentation = true + "" + when "enddoc" + #context.done_documenting = true + #"" + throw :enddoc + when "main" + options = Options.instance + options.main_page = param + "" + when "title" + options = Options.instance + options.title = param + "" + when "section" + context.set_current_section(param, comment) + comment.replace("") # 1.8 doesn't support #clear + break + else + warn "Unrecognized directive '#{directive}'" + break + end + end + remove_private_comments(comment) + end + + def remove_private_comments(comment) + comment.gsub!(/^#--.*?^#\+\+/m, '') + comment.sub!(/^#--.*/m, '') + end +end diff --git a/lib/puppet/util/rdoc/parser/puppet_parser_rdoc1.rb b/lib/puppet/util/rdoc/parser/puppet_parser_rdoc1.rb new file mode 100644 index 000000000..656d2f7e7 --- /dev/null +++ b/lib/puppet/util/rdoc/parser/puppet_parser_rdoc1.rb @@ -0,0 +1,19 @@ +require 'puppet/util/rdoc/parser/puppet_parser_core.rb' + +module RDoc + PUPPET_RDOC_VERSION = 1 + + # @api private + class PuppetParserRDoc1 + extend ParserFactory + include PuppetParserCore + + def create_rdoc_preprocess + preprocess = SM::PreProcess.new(@input_file_name, @options.rdoc_include) + end + end + + # For backwards compatibility + # @api private + Parser = PuppetParserRDoc1 +end diff --git a/lib/puppet/util/rdoc/parser/puppet_parser_rdoc2.rb b/lib/puppet/util/rdoc/parser/puppet_parser_rdoc2.rb new file mode 100644 index 000000000..75a5cdac3 --- /dev/null +++ b/lib/puppet/util/rdoc/parser/puppet_parser_rdoc2.rb @@ -0,0 +1,14 @@ +require 'puppet/util/rdoc/parser/puppet_parser_core.rb' + +module RDoc + PUPPET_RDOC_VERSION = 2 + + # @api private + class PuppetParserRDoc2 < Parser + include PuppetParserCore + + def create_rdoc_preprocess + preprocess = Markup::PreProcess.new(@input_file_name, @options.rdoc_include) + end + end +end diff --git a/lib/puppet/util/reference.rb b/lib/puppet/util/reference.rb index 2e2ee78f6..491e39b09 100644 --- a/lib/puppet/util/reference.rb +++ b/lib/puppet/util/reference.rb @@ -46,7 +46,7 @@ class Puppet::Util::Reference # There used to be an attempt to use secure_open / replace_file to secure # the target, too, but that did nothing: the race was still here. We can # get exactly the same benefit from running this effort: - File.unlink('/tmp/puppetdoc.tex') rescue nil + Puppet::FileSystem::File.unlink('/tmp/puppetdoc.tex') rescue nil output = %x{#{cmd}} unless $CHILD_STATUS == 0 $stderr.puts "rst2latex failed" diff --git a/lib/puppet/util/resource_template.rb b/lib/puppet/util/resource_template.rb index b12b125b5..bed585b21 100644 --- a/lib/puppet/util/resource_template.rb +++ b/lib/puppet/util/resource_template.rb @@ -44,7 +44,7 @@ class Puppet::Util::ResourceTemplate end def initialize(file, resource) - raise ArgumentError, "Template #{file} does not exist" unless FileTest.exist?(file) + raise ArgumentError, "Template #{file} does not exist" unless Puppet::FileSystem::File.exist?(file) @file = file @resource = resource end diff --git a/lib/puppet/util/selinux.rb b/lib/puppet/util/selinux.rb index 0b712be0d..78c3c4dfa 100644 --- a/lib/puppet/util/selinux.rb +++ b/lib/puppet/util/selinux.rb @@ -216,7 +216,7 @@ module Puppet::Util::SELinux # # @return [File::Stat] File.lstat result def file_lstat(path) - File.lstat(path) + Puppet::FileSystem::File.new(path).lstat end private :file_lstat end diff --git a/lib/puppet/util/storage.rb b/lib/puppet/util/storage.rb index 32d744862..9df1cb501 100644 --- a/lib/puppet/util/storage.rb +++ b/lib/puppet/util/storage.rb @@ -45,7 +45,7 @@ class Puppet::Util::Storage Puppet.settings.use(:main) unless FileTest.directory?(Puppet[:statedir]) filename = Puppet[:statefile] - unless File.exists?(filename) + unless Puppet::FileSystem::File.exist?(filename) self.init if @@state.nil? return end @@ -80,7 +80,7 @@ class Puppet::Util::Storage def self.store Puppet.debug "Storing state" - Puppet.info "Creating state file #{Puppet[:statefile]}" unless FileTest.exist?(Puppet[:statefile]) + Puppet.info "Creating state file #{Puppet[:statefile]}" unless Puppet::FileSystem::File.exist?(Puppet[:statefile]) Puppet::Util.benchmark(:debug, "Stored state") do Puppet::Util::Yaml.dump(@@state, Puppet[:statefile]) diff --git a/lib/puppet/util/suidmanager.rb b/lib/puppet/util/suidmanager.rb index e2b063992..481e3864c 100644 --- a/lib/puppet/util/suidmanager.rb +++ b/lib/puppet/util/suidmanager.rb @@ -178,7 +178,7 @@ module Puppet::Util::SUIDManager # :custom_environment (default {}) -- a hash of key/value pairs to set as environment variables for the duration # of the command def run_and_capture(command, new_uid=nil, new_gid=nil, options = {}) - + Puppet.deprecation_warning("Puppet::Util::SUIDManager.run_and_capture is deprecated; please use Puppet::Util::Execution.execute instead.") # specifying these here rather than in the method signature to allow callers to pass in a partial # set of overrides without affecting the default values for options that they don't pass in default_options = { diff --git a/lib/puppet/util/tag_set.rb b/lib/puppet/util/tag_set.rb new file mode 100644 index 000000000..bd1029040 --- /dev/null +++ b/lib/puppet/util/tag_set.rb @@ -0,0 +1,29 @@ +require 'set' + +class Puppet::Util::TagSet < Set + def self.from_yaml(yaml) + self.new(YAML.load(yaml)) + end + + def to_yaml + @hash.keys.to_yaml + end + + def self.from_pson(data) + self.new(data) + end + + def to_pson(*args) + to_a.to_pson + end + + # this makes puppet serialize it as an array for backwards + # compatibility + def to_zaml(z) + to_a.to_zaml(z) + end + + def join(*args) + to_a.join(*args) + end +end diff --git a/lib/puppet/util/tagging.rb b/lib/puppet/util/tagging.rb index 4161c0291..2e788279b 100644 --- a/lib/puppet/util/tagging.rb +++ b/lib/puppet/util/tagging.rb @@ -1,30 +1,10 @@ -# Created on 2008-01-19 -# Copyright Luke Kanies +require 'puppet/util/tag_set' -# A common module to handle tagging. -# -# So, do you want the bad news or the good news first? -# -# The bad news is that using an array here is hugely costly compared to using -# a hash. Like, the same speed empty, 50 percent slower with one item, and -# 300 percent slower at 6 - one of our common peaks for tagging items. -# -# ...and that assumes an efficient implementation, just using include?. These -# methods have even more costs hidden in them. -# -# The good news is that this module has no API. Various objects directly -# interact with their `@tags` member as an array, or dump it directly in YAML, -# or whatever. -# -# So, er, you can't actually change this. No matter how much you want to be -# cause it is inefficient in both CPU and object allocation terms. -# -# Good luck, my friend. --daniel 2012-07-17 module Puppet::Util::Tagging # Add a tag to our current list. These tags will be added to all # of the objects contained in this scope. def tag(*ary) - @tags ||= [] + @tags ||= new_tags qualified = [] @@ -45,12 +25,12 @@ module Puppet::Util::Tagging # Return a copy of the tag list, so someone can't ask for our tags # and then modify them. def tags - @tags ||= [] + @tags ||= new_tags @tags.dup end def tags=(tags) - @tags = [] + @tags = new_tags return if tags.nil? or tags == "" @@ -73,4 +53,8 @@ module Puppet::Util::Tagging def valid_tag?(tag) tag.is_a?(String) and tag =~ ValidTagRegex end + + def new_tags + Puppet::Util::TagSet.new + end end diff --git a/lib/puppet/util/watched_file.rb b/lib/puppet/util/watched_file.rb index 6b28ab402..3e1195700 100755..100644 --- a/lib/puppet/util/watched_file.rb +++ b/lib/puppet/util/watched_file.rb @@ -26,7 +26,7 @@ class Puppet::Util::WatchedFile end # Allow this to be used as the name of the file being watched in various - # other methods (such as File.exist?) + # other methods (such as Puppet::FileSystem::File.exist?) def to_str @filename end diff --git a/lib/puppet/util/watcher.rb b/lib/puppet/util/watcher.rb index 78b21f8af..547c24c9e 100644 --- a/lib/puppet/util/watcher.rb +++ b/lib/puppet/util/watcher.rb @@ -7,7 +7,7 @@ module Puppet::Util::Watcher def self.file_ctime_change_watcher(filename) Puppet::Util::Watcher::ChangeWatcher.watch(lambda do begin - File.stat(filename).ctime + Puppet::FileSystem::File.new(filename).stat.ctime rescue Errno::ENOENT, Errno::ENOTDIR :absent end diff --git a/lib/puppet/util/windows.rb b/lib/puppet/util/windows.rb index 612f72b78..af440552f 100644 --- a/lib/puppet/util/windows.rb +++ b/lib/puppet/util/windows.rb @@ -8,6 +8,9 @@ module Puppet::Util::Windows require 'puppet/util/windows/process' require 'puppet/util/windows/file' require 'puppet/util/windows/root_certs' + require 'puppet/util/windows/access_control_entry' + require 'puppet/util/windows/access_control_list' + require 'puppet/util/windows/security_descriptor' end require 'puppet/util/windows/registry' end diff --git a/lib/puppet/util/windows/access_control_entry.rb b/lib/puppet/util/windows/access_control_entry.rb new file mode 100644 index 000000000..5cd052dae --- /dev/null +++ b/lib/puppet/util/windows/access_control_entry.rb @@ -0,0 +1,84 @@ +# Windows Access Control Entry +# +# Represents an access control entry, which grants or denies a subject, +# identified by a SID, rights to a securable object. +# +# @see http://msdn.microsoft.com/en-us/library/windows/desktop/aa374868(v=vs.85).aspx +# @api private +class Puppet::Util::Windows::AccessControlEntry + require 'puppet/util/windows/security' + include Puppet::Util::Windows::SID + + attr_accessor :sid + attr_reader :mask, :flags, :type + + OBJECT_INHERIT_ACE = 0x1 + CONTAINER_INHERIT_ACE = 0x2 + NO_PROPAGATE_INHERIT_ACE = 0x4 + INHERIT_ONLY_ACE = 0x8 + INHERITED_ACE = 0x10 + + ACCESS_ALLOWED_ACE_TYPE = 0x0 + ACCESS_DENIED_ACE_TYPE = 0x1 + + def initialize(sid, mask, flags = 0, type = ACCESS_ALLOWED_ACE_TYPE) + @sid = sid + @mask = mask + @flags = flags + @type = type + end + + # Returns true if this ACE is inherited from a parent. If false, + # then the ACE is set directly on the object to which it refers. + # + # @return [Boolean] true if the ACE is inherited + def inherited? + (@flags & INHERITED_ACE) == INHERITED_ACE + end + + # Returns true if this ACE only applies to children of the object. + # If false, it applies to the object. + # + # @return [Boolean] true if the ACE only applies to children and + # not the object itself. + def inherit_only? + (@flags & INHERIT_ONLY_ACE) == INHERIT_ONLY_ACE + end + + # Returns true if this ACE applies to child directories. + # + # @return [Boolean] true if the ACE applies to child direcories + def container_inherit? + (@flags & CONTAINER_INHERIT_ACE) == CONTAINER_INHERIT_ACE + end + + # Returns true if this ACE applies to child files. + # + # @return [Boolean] true if the ACE applies to child files. + def object_inherit? + (@flags & OBJECT_INHERIT_ACE) == OBJECT_INHERIT_ACE + end + + def inspect + inheritance = "" + inheritance << '(I)' if inherited? + inheritance << '(OI)' if object_inherit? + inheritance << '(CI)' if container_inherit? + inheritance << '(IO)' if inherit_only? + + left = "#{sid_to_name(sid)}:#{inheritance}" + left = left.ljust(45) + "#{left} 0x#{mask.to_s(16)}" + end + + # Returns true if this ACE is equal to +other+ + def ==(other) + self.class == other.class && + sid == other.sid && + mask == other.mask && + flags == other.flags && + type == other.type + end + + alias eql? == +end diff --git a/lib/puppet/util/windows/access_control_list.rb b/lib/puppet/util/windows/access_control_list.rb new file mode 100644 index 000000000..14a924cd9 --- /dev/null +++ b/lib/puppet/util/windows/access_control_list.rb @@ -0,0 +1,106 @@ +# Windows Access Control List +# +# Represents a list of access control entries (ACEs). +# +# @see http://msdn.microsoft.com/en-us/library/windows/desktop/aa374872(v=vs.85).aspx +# @api private +class Puppet::Util::Windows::AccessControlList + include Enumerable + + ACCESS_ALLOWED_ACE_TYPE = 0x0 + ACCESS_DENIED_ACE_TYPE = 0x1 + + # Construct an ACL. + # + # @param acl [Enumerable] A list of aces to copy from. + def initialize(acl = nil) + if acl + @aces = acl.map(&:dup) + else + @aces = [] + end + end + + # Enumerate each ACE in the list. + # + # @yieldparam ace [Hash] the ace + def each + @aces.each {|ace| yield ace} + end + + # Allow the +sid+ to access a resource with the specified access +mask+. + # + # @param sid [String] The SID that the ACE is granting access to + # @param mask [int] The access mask granted to the SID + # @param flags [int] The flags assigned to the ACE, e.g. +INHERIT_ONLY_ACE+ + def allow(sid, mask, flags = 0) + @aces << Puppet::Util::Windows::AccessControlEntry.new(sid, mask, flags, ACCESS_ALLOWED_ACE_TYPE) + end + + # Deny the +sid+ access to a resource with the specified access +mask+. + # + # @param sid [String] The SID that the ACE is denying access to + # @param mask [int] The access mask denied to the SID + # @param flags [int] The flags assigned to the ACE, e.g. +INHERIT_ONLY_ACE+ + def deny(sid, mask, flags = 0) + @aces << Puppet::Util::Windows::AccessControlEntry.new(sid, mask, flags, ACCESS_DENIED_ACE_TYPE) + end + + # Reassign all ACEs currently assigned to +old_sid+ to +new_sid+ instead. + # If an ACE is inherited or is not assigned to +old_sid+, then it will + # be copied as-is to the new ACL, preserving its order within the ACL. + # + # @param old_sid [String] The old SID, e.g. 'S-1-5-18' + # @param new_sid [String] The new SID + # @return [AccessControlList] The copied ACL. + def reassign!(old_sid, new_sid) + new_aces = [] + prepend_needed = false + aces_to_prepend = [] + + @aces.each do |ace| + new_ace = ace.dup + + if ace.sid == old_sid + if ace.inherited? + # create an explicit ACE granting or denying the + # new_sid the rights that the inherited ACE + # granted or denied the old_sid. We mask off all + # flags except those affecting inheritance of the + # ACE we're creating. + inherit_mask = Windows::Security::CONTAINER_INHERIT_ACE | + Windows::Security::OBJECT_INHERIT_ACE | + Windows::Security::INHERIT_ONLY_ACE + explicit_ace = Puppet::Util::Windows::AccessControlEntry.new(new_sid, ace.mask, ace.flags & inherit_mask, ace.type) + aces_to_prepend << explicit_ace + else + new_ace.sid = new_sid + + prepend_needed = old_sid == Win32::Security::SID::LocalSystem + end + end + new_aces << new_ace + end + + @aces = [] + + if prepend_needed + mask = Windows::Security::STANDARD_RIGHTS_ALL | Windows::Security::SPECIFIC_RIGHTS_ALL + ace = Puppet::Util::Windows::AccessControlEntry.new( + Win32::Security::SID::LocalSystem, + mask) + @aces << ace + end + + @aces.concat(aces_to_prepend) + @aces.concat(new_aces) + end + + def inspect + str = "" + @aces.each do |ace| + str << " #{ace.inspect}\n" + end + str + end +end diff --git a/lib/puppet/util/windows/file.rb b/lib/puppet/util/windows/file.rb index d4b7bc1ca..141873071 100644 --- a/lib/puppet/util/windows/file.rb +++ b/lib/puppet/util/windows/file.rb @@ -1,6 +1,7 @@ require 'puppet/util/windows' module Puppet::Util::Windows::File + require 'ffi' require 'windows/api' require 'windows/wide_string' @@ -24,4 +25,216 @@ module Puppet::Util::Windows::File new("MoveFileEx(#{source}, #{target}, #{flags.to_s(8)})") end module_function :move_file_ex + + module API + extend FFI::Library + ffi_lib 'kernel32' + ffi_convention :stdcall + + # BOOLEAN WINAPI CreateSymbolicLink( + # _In_ LPTSTR lpSymlinkFileName, - symbolic link to be created + # _In_ LPTSTR lpTargetFileName, - name of target for symbolic link + # _In_ DWORD dwFlags - 0x0 target is a file, 0x1 target is a directory + # ); + # rescue on Windows < 6.0 so that code doesn't explode + begin + attach_function :create_symbolic_link, :CreateSymbolicLinkW, + [:buffer_in, :buffer_in, :uint], :bool + rescue LoadError + end + + # DWORD WINAPI GetFileAttributes( + # _In_ LPCTSTR lpFileName + # ); + attach_function :get_file_attributes, :GetFileAttributesW, + [:buffer_in], :uint + + # HANDLE WINAPI CreateFile( + # _In_ LPCTSTR lpFileName, + # _In_ DWORD dwDesiredAccess, + # _In_ DWORD dwShareMode, + # _In_opt_ LPSECURITY_ATTRIBUTES lpSecurityAttributes, + # _In_ DWORD dwCreationDisposition, + # _In_ DWORD dwFlagsAndAttributes, + # _In_opt_ HANDLE hTemplateFile + # ); + attach_function :create_file, :CreateFileW, + [:buffer_in, :uint, :uint, :pointer, :uint, :uint, :uint], :uint + + # http://msdn.microsoft.com/en-us/library/windows/desktop/aa363216(v=vs.85).aspx + # BOOL WINAPI DeviceIoControl( + # _In_ HANDLE hDevice, + # _In_ DWORD dwIoControlCode, + # _In_opt_ LPVOID lpInBuffer, + # _In_ DWORD nInBufferSize, + # _Out_opt_ LPVOID lpOutBuffer, + # _In_ DWORD nOutBufferSize, + # _Out_opt_ LPDWORD lpBytesReturned, + # _Inout_opt_ LPOVERLAPPED lpOverlapped + # ); + attach_function :device_io_control, :DeviceIoControl, + [:uint, :uint, :pointer, :uint, :pointer, :uint, :pointer, :pointer], :bool + + MAXIMUM_REPARSE_DATA_BUFFER_SIZE = 16384 + + # REPARSE_DATA_BUFFER + # http://msdn.microsoft.com/en-us/library/cc232006.aspx + # http://msdn.microsoft.com/en-us/library/windows/hardware/ff552012(v=vs.85).aspx + # struct is always MAXIMUM_REPARSE_DATA_BUFFER_SIZE bytes + class ReparseDataBuffer < FFI::Struct + layout :reparse_tag, :uint, + :reparse_data_length, :ushort, + :reserved, :ushort, + :substitute_name_offset, :ushort, + :substitute_name_length, :ushort, + :print_name_offset, :ushort, + :print_name_length, :ushort, + :flags, :uint, + # max less above fields dword / uint 4 bytes, ushort 2 bytes + :path_buffer, [:uchar, MAXIMUM_REPARSE_DATA_BUFFER_SIZE - 20] + end + + # BOOL WINAPI CloseHandle( + # _In_ HANDLE hObject + # ); + attach_function :close_handle, :CloseHandle, [:uint], :bool + end + + def symlink(target, symlink) + flags = File.directory?(target) ? 0x1 : 0x0 + result = API.create_symbolic_link(WideString.new(symlink.to_s), + WideString.new(target.to_s), flags) + return true if result + raise Puppet::Util::Windows::Error.new( + "CreateSymbolicLink(#{symlink}, #{target}, #{flags.to_s(8)})") + end + module_function :symlink + + INVALID_FILE_ATTRIBUTES = 0xFFFFFFFF #define INVALID_FILE_ATTRIBUTES (DWORD (-1)) + def self.get_file_attributes(file_name) + result = API.get_file_attributes(WideString.new(file_name.to_s)) + return result unless result == INVALID_FILE_ATTRIBUTES + raise Puppet::Util::Windows::Error.new("GetFileAttributes(#{file_name})") + end + + INVALID_HANDLE_VALUE = -1 #define INVALID_HANDLE_VALUE ((HANDLE)(LONG_PTR)-1) + def self.create_file(file_name, desired_access, share_mode, security_attributes, + creation_disposition, flags_and_attributes, template_file_handle) + + result = API.create_file(WideString.new(file_name.to_s), + desired_access, share_mode, security_attributes, creation_disposition, + flags_and_attributes, template_file_handle) + + return result unless result == INVALID_HANDLE_VALUE + raise Puppet::Util::Windows::Error.new( + "CreateFile(#{file_name}, #{desired_access.to_s(8)}, #{share_mode.to_s(8)}, " + + "#{security_attributes}, #{creation_disposition.to_s(8)}, " + + "#{flags_and_attributes.to_s(8)}, #{template_file_handle})") + end + + def self.device_io_control(handle, io_control_code, in_buffer = nil, out_buffer = nil) + if out_buffer.nil? + raise Puppet::Util::Windows::Error.new("out_buffer is required") + end + + result = API.device_io_control( + handle, + io_control_code, + in_buffer, in_buffer.nil? ? 0 : in_buffer.size, + out_buffer, out_buffer.size, + FFI::MemoryPointer.new(:uint, 1), + nil + ) + + return out_buffer if result + raise Puppet::Util::Windows::Error.new( + "DeviceIoControl(#{handle}, #{io_control_code}, #{in_buffer}, #{in_buffer.size}, " + + "#{out_buffer}, #{out_buffer.size}") + end + + FILE_ATTRIBUTE_REPARSE_POINT = 0x400 + def symlink?(file_name) + begin + attributes = get_file_attributes(file_name) + (attributes & FILE_ATTRIBUTE_REPARSE_POINT) == FILE_ATTRIBUTE_REPARSE_POINT + rescue + # raised INVALID_FILE_ATTRIBUTES is equivalent to file not found + false + end + end + module_function :symlink? + + GENERIC_READ = 0x80000000 + FILE_SHARE_READ = 1 + OPEN_EXISTING = 3 + FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000 + FILE_FLAG_BACKUP_SEMANTICS = 0x02000000 + + def self.open_symlink(link_name) + begin + yield handle = create_file( + WideString.new(link_name.to_s), + GENERIC_READ, + FILE_SHARE_READ, + nil, # security_attributes + OPEN_EXISTING, + FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, + 0) # template_file + ensure + API.close_handle(handle) if handle + end + end + + def readlink(link_name) + open_symlink(link_name) do |handle| + resolve_symlink(handle) + end + end + module_function :readlink + + def stat(file_name) + file_name = file_name.to_s # accomodate PathName or String + stat = File.stat(file_name) + if symlink?(file_name) + link_ftype = File.stat(readlink(file_name)).ftype + # sigh, monkey patch instance method for instance, and close over link_ftype + singleton_class = class << stat; self; end + singleton_class.send(:define_method, :ftype) do + link_ftype + end + end + stat + end + module_function :stat + + def lstat(file_name) + file_name = file_name.to_s # accomodate PathName or String + # monkey'ing around! + stat = File.lstat(file_name) + if symlink?(file_name) + def stat.ftype + "link" + end + end + stat + end + module_function :lstat + + private + + # http://msdn.microsoft.com/en-us/library/windows/desktop/aa364571(v=vs.85).aspx + FSCTL_GET_REPARSE_POINT = 0x900a8 + + def self.resolve_symlink(handle) + # must be multiple of 1024, min 10240 + out_buffer = FFI::MemoryPointer.new(API::ReparseDataBuffer.size) + device_io_control(handle, FSCTL_GET_REPARSE_POINT, nil, out_buffer) + + reparse_data = API::ReparseDataBuffer.new(out_buffer) + offset = reparse_data[:print_name_offset] + length = reparse_data[:print_name_length] + + result = reparse_data[:path_buffer].to_a[offset, length].pack('C*') + result.force_encoding('UTF-16LE').encode(Encoding.default_external) + end end diff --git a/lib/puppet/util/windows/process.rb b/lib/puppet/util/windows/process.rb index fc85119c3..165e90af0 100644 --- a/lib/puppet/util/windows/process.rb +++ b/lib/puppet/util/windows/process.rb @@ -8,6 +8,117 @@ module Puppet::Util::Windows::Process extend ::Windows::Handle extend ::Windows::Synchronize + module API + require 'ffi' + extend FFI::Library + ffi_convention :stdcall + + ffi_lib 'kernel32' + + # http://msdn.microsoft.com/en-us/library/windows/desktop/ms683179(v=vs.85).aspx + # HANDLE WINAPI GetCurrentProcess(void); + attach_function :get_current_process, :GetCurrentProcess, [], :uint + + # BOOL WINAPI CloseHandle( + # _In_ HANDLE hObject + # ); + attach_function :close_handle, :CloseHandle, [:uint], :bool + + ffi_lib 'advapi32' + + # http://msdn.microsoft.com/en-us/library/windows/desktop/aa379295(v=vs.85).aspx + # BOOL WINAPI OpenProcessToken( + # _In_ HANDLE ProcessHandle, + # _In_ DWORD DesiredAccess, + # _Out_ PHANDLE TokenHandle + # ); + attach_function :open_process_token, :OpenProcessToken, + [:uint, :uint, :pointer], :bool + + + # http://msdn.microsoft.com/en-us/library/windows/desktop/aa379261(v=vs.85).aspx + # typedef struct _LUID { + # DWORD LowPart; + # LONG HighPart; + # } LUID, *PLUID; + class LUID < FFI::Struct + layout :low_part, :uint, + :high_part, :int + end + + # http://msdn.microsoft.com/en-us/library/Windows/desktop/aa379180(v=vs.85).aspx + # BOOL WINAPI LookupPrivilegeValue( + # _In_opt_ LPCTSTR lpSystemName, + # _In_ LPCTSTR lpName, + # _Out_ PLUID lpLuid + # ); + attach_function :lookup_privilege_value, :LookupPrivilegeValueW, + [:buffer_in, :buffer_in, :pointer], :bool + + Token_Information = enum( + :token_user, 1, + :token_groups, + :token_privileges, + :token_owner, + :token_primary_group, + :token_default_dacl, + :token_source, + :token_type, + :token_impersonation_level, + :token_statistics, + :token_restricted_sids, + :token_session_id, + :token_groups_and_privileges, + :token_session_reference, + :token_sandbox_inert, + :token_audit_policy, + :token_origin, + :token_elevation_type, + :token_linked_token, + :token_elevation, + :token_has_restrictions, + :token_access_information, + :token_virtualization_allowed, + :token_virtualization_enabled, + :token_integrity_level, + :token_ui_access, + :token_mandatory_policy, + :token_logon_sid, + :max_token_info_class + ) + + # http://msdn.microsoft.com/en-us/library/windows/desktop/aa379263(v=vs.85).aspx + # typedef struct _LUID_AND_ATTRIBUTES { + # LUID Luid; + # DWORD Attributes; + # } LUID_AND_ATTRIBUTES, *PLUID_AND_ATTRIBUTES; + class LUID_And_Attributes < FFI::Struct + layout :luid, LUID, + :attributes, :uint + end + + # http://msdn.microsoft.com/en-us/library/windows/desktop/aa379630(v=vs.85).aspx + # typedef struct _TOKEN_PRIVILEGES { + # DWORD PrivilegeCount; + # LUID_AND_ATTRIBUTES Privileges[ANYSIZE_ARRAY]; + # } TOKEN_PRIVILEGES, *PTOKEN_PRIVILEGES; + class Token_Privileges < FFI::Struct + layout :privilege_count, :uint, + :privileges, [LUID_And_Attributes, 1] # placeholder for offset + end + + # http://msdn.microsoft.com/en-us/library/windows/desktop/aa446671(v=vs.85).aspx + # BOOL WINAPI GetTokenInformation( + # _In_ HANDLE TokenHandle, + # _In_ TOKEN_INFORMATION_CLASS TokenInformationClass, + # _Out_opt_ LPVOID TokenInformation, + # _In_ DWORD TokenInformationLength, + # _Out_ PDWORD ReturnLength + # ); + attach_function :get_token_information, :GetTokenInformation, + [:uint, Token_Information, :pointer, :uint, :pointer ], :bool + end + def execute(command, arguments, stdin, stdout, stderr) Process.create( :command_line => command, :startup_info => {:stdin => stdin, :stdout => stdout, :stderr => stderr}, :close_handles => false ) end @@ -33,4 +144,92 @@ module Puppet::Util::Windows::Process exit_status end module_function :wait_process + + def get_current_process + # this pseudo-handle does not require closing per MSDN docs + API.get_current_process + end + module_function :get_current_process + + def open_process_token(handle, desired_access) + token_handle_ptr = FFI::MemoryPointer.new(:uint, 1) + result = API.open_process_token(handle, desired_access, token_handle_ptr) + if !result + raise Puppet::Util::Windows::Error.new( + "OpenProcessToken(#{handle}, #{desired_access.to_s(8)}, #{token_handle_ptr})") + end + + begin + yield token_handle = token_handle_ptr.read_uint + ensure + API.close_handle(token_handle) + end + end + module_function :open_process_token + + def lookup_privilege_value(name, system_name = '') + luid = FFI::MemoryPointer.new(API::LUID.size) + result = API.lookup_privilege_value(WideString.new(system_name), + WideString.new(name.to_s), luid) + + return API::LUID.new(luid) if result + raise Puppet::Util::Windows::Error.new( + "LookupPrivilegeValue(#{system_name}, #{name}, #{luid})") + end + module_function :lookup_privilege_value + + def get_token_information(token_handle, token_information) + # to determine buffer size + return_length_ptr = FFI::MemoryPointer.new(:uint, 1) + result = API.get_token_information(token_handle, token_information, nil, 0, return_length_ptr) + return_length = return_length_ptr.read_uint + + if return_length <= 0 + raise Puppet::Util::Windows::Error.new( + "GetTokenInformation(#{token_handle}, #{token_information}, nil, 0, #{return_length_ptr})") + end + + # re-call API with properly sized buffer for all results + token_information_buf = FFI::MemoryPointer.new(return_length) + result = API.get_token_information(token_handle, token_information, + token_information_buf, return_length, return_length_ptr) + + if !result + raise Puppet::Util::Windows::Error.new( + "GetTokenInformation(#{token_handle}, #{token_information}, #{token_information_buf}, " + + "#{return_length}, #{return_length_ptr})") + end + + raw_privileges = API::Token_Privileges.new(token_information_buf) + privileges = { :count => raw_privileges[:privilege_count], :privileges => [] } + + offset = token_information_buf + API::Token_Privileges.offset_of(:privileges) + privilege_ptr = FFI::Pointer.new(API::LUID_And_Attributes, offset) + + # extract each instance of LUID_And_Attributes + 0.upto(privileges[:count] - 1) do |i| + privileges[:privileges] << API::LUID_And_Attributes.new(privilege_ptr[i]) + end + + privileges + end + module_function :get_token_information + + TOKEN_ALL_ACCESS = 0xF01FF + ERROR_NO_SUCH_PRIVILEGE = 1313 + def process_privilege_symlink? + handle = get_current_process + open_process_token(handle, TOKEN_ALL_ACCESS) do |token_handle| + luid = lookup_privilege_value('SeCreateSymbolicLinkPrivilege') + token_info = get_token_information(token_handle, :token_privileges) + token_info[:privileges].any? { |p| p[:luid].values == luid.values } + end + rescue Puppet::Util::Windows::Error => e + if e.code == ERROR_NO_SUCH_PRIVILEGE + false # pre-Vista + else + raise e + end + end + module_function :process_privilege_symlink? end diff --git a/lib/puppet/util/windows/root_certs.rb b/lib/puppet/util/windows/root_certs.rb index 4eae0a540..7988ab832 100644 --- a/lib/puppet/util/windows/root_certs.rb +++ b/lib/puppet/util/windows/root_certs.rb @@ -1,17 +1,16 @@ require 'puppet/util/windows' require 'openssl' -require 'Win32API' -require 'windows/msvcrt/buffer' +require 'ffi' # Represents a collection of trusted root certificates. # # @api public class Puppet::Util::Windows::RootCerts include Enumerable + extend FFI::Library - CertOpenSystemStore = Win32API.new('crypt32', 'CertOpenSystemStore', ['L','P'], 'L') - CertEnumCertificatesInStore = Win32API.new('crypt32', 'CertEnumCertificatesInStore', ['L', 'L'], 'L') - CertCloseStore = Win32API.new('crypt32', 'CertCloseStore', ['L', 'L'], 'B') + typedef :ulong, :dword + typedef :uintptr_t, :handle def initialize(roots) @roots = roots @@ -24,10 +23,6 @@ class Puppet::Util::Windows::RootCerts @roots.each {|cert| yield cert} end - class << self - include Windows::MSVCRT::Buffer - end - # Returns a new instance. # @return [Puppet::Util::Windows::RootCerts] object constructed from current root certificates def self.instance @@ -43,34 +38,12 @@ class Puppet::Util::Windows::RootCerts # This is based on a patch submitted to openssl: # http://www.mail-archive.com/openssl-dev@openssl.org/msg26958.html - context = 0 - store = CertOpenSystemStore.call(0, "ROOT") + ptr = FFI::Pointer::NULL + store = CertOpenSystemStoreA(nil, "ROOT") begin - while (context = CertEnumCertificatesInStore.call(store, context) and context != 0) - # 466 typedef struct _CERT_CONTEXT { - # 467 DWORD dwCertEncodingType; - # 468 BYTE *pbCertEncoded; - # 469 DWORD cbCertEncoded; - # 470 PCERT_INFO pCertInfo; - # 471 HCERTSTORE hCertStore; - # 472 } CERT_CONTEXT, *PCERT_CONTEXT; - - # buffer to hold struct above - ctx_buf = 0.chr * 5 * 8 - - # copy from win to ruby - memcpy(ctx_buf, context, ctx_buf.size) - - # unpack structure - arr = ctx_buf.unpack('LLLLL') - - # create buf of length cbCertEncoded - cert_buf = 0.chr * arr[2] - - # copy pbCertEncoded from win to ruby - memcpy(cert_buf, arr[1], cert_buf.length) - - # create a cert + while (ptr = CertEnumCertificatesInStore(store, ptr)) and not ptr.null? + context = CERT_CONTEXT.new(ptr) + cert_buf = context[:pbCertEncoded].read_bytes(context[:cbCertEncoded]) begin certs << OpenSSL::X509::Certificate.new(cert_buf) rescue => detail @@ -78,9 +51,51 @@ class Puppet::Util::Windows::RootCerts end end ensure - CertCloseStore.call(store, 0) + CertCloseStore(store, 0) end certs end + + private + + # typedef ULONG_PTR HCRYPTPROV_LEGACY; + # typedef void *HCERTSTORE; + + class CERT_CONTEXT < FFI::Struct + layout( + :dwCertEncodingType, :dword, + :pbCertEncoded, :pointer, + :cbCertEncoded, :dword, + :pCertInfo, :pointer, + :hCertStore, :handle + ) + end + + # HCERTSTORE + # WINAPI + # CertOpenSystemStoreA( + # __in_opt HCRYPTPROV_LEGACY hProv, + # __in LPCSTR szSubsystemProtocol + # ); + ffi_lib :crypt32 + attach_function :CertOpenSystemStoreA, [:pointer, :string], :handle + + # PCCERT_CONTEXT + # WINAPI + # CertEnumCertificatesInStore( + # __in HCERTSTORE hCertStore, + # __in_opt PCCERT_CONTEXT pPrevCertContext + # ); + ffi_lib :crypt32 + attach_function :CertEnumCertificatesInStore, [:handle, :pointer], :pointer + + # BOOL + # WINAPI + # CertCloseStore( + # __in_opt HCERTSTORE hCertStore, + # __in DWORD dwFlags + # ); + ffi_lib :crypt32 + attach_function :CertCloseStore, [:handle, :dword], :bool end diff --git a/lib/puppet/util/windows/security.rb b/lib/puppet/util/windows/security.rb index 92df1b746..dfd7ea931 100644 --- a/lib/puppet/util/windows/security.rb +++ b/lib/puppet/util/windows/security.rb @@ -53,6 +53,8 @@ # enables Puppet to detect when file/dirs are out-of-sync, # especially those that Puppet did not create, but is attempting # to manage. +# * A special case of this is S_ISYSTEM_MISSING, which is set when the +# SYSTEM permissions are *not* present on the DACL. # * On Unix, the owner and group can be modified without changing the # mode. But on Windows, an access control entry specifies which SID # it applies to. As a result, the set_owner and set_group methods @@ -61,6 +63,7 @@ require 'puppet/util/windows' require 'pathname' +require 'ffi' require 'win32/security' @@ -100,11 +103,13 @@ module Puppet::Util::Windows::Security S_IRWXO = 0000007 S_ISVTX = 0001000 S_IEXTRA = 02000000 # represents an extra ace + S_ISYSTEM_MISSING = 04000000 # constants that are missing from Windows::Security PROTECTED_DACL_SECURITY_INFORMATION = 0x80000000 UNPROTECTED_DACL_SECURITY_INFORMATION = 0x20000000 NO_INHERITANCE = 0x0 + SE_DACL_PROTECTED = 0x1000 # Set the owner of the object referenced by +path+ to the specified # +owner_sid+. The owner sid should be of the form "S-1-5-32-544" @@ -112,9 +117,12 @@ module Puppet::Util::Windows::Security # SE_RESTORE_NAME privilege in their process token can overwrite the # object's owner to something other than the current user. def set_owner(owner_sid, path) - old_sid = get_owner(path) + sd = get_security_descriptor(path) - change_sid(old_sid, owner_sid, OWNER_SECURITY_INFORMATION, path) + if owner_sid != sd.owner + sd.owner = owner_sid + set_security_descriptor(path, sd) + end end # Get the owner of the object referenced by +path+. The returned @@ -125,7 +133,7 @@ module Puppet::Util::Windows::Security def get_owner(path) return unless supports_acl?(path) - get_sid(OWNER_SECURITY_INFORMATION, path) + get_security_descriptor(path).owner end # Set the owner of the object referenced by +path+ to the specified @@ -134,9 +142,12 @@ module Puppet::Util::Windows::Security # access to the object can change the group (regardless of whether # the current user belongs to that group or not). def set_group(group_sid, path) - old_sid = get_group(path) + sd = get_security_descriptor(path) - change_sid(old_sid, group_sid, GROUP_SECURITY_INFORMATION, path) + if group_sid != sd.group + sd.group = group_sid + set_security_descriptor(path, sd) + end end # Get the group of the object referenced by +path+. The returned @@ -147,7 +158,7 @@ module Puppet::Util::Windows::Security def get_group(path) return unless supports_acl?(path) - get_sid(GROUP_SECURITY_INFORMATION, path) + get_security_descriptor(path).group end def supports_acl?(path) @@ -163,31 +174,6 @@ module Puppet::Util::Windows::Security (flags.unpack('L')[0] & Windows::File::FILE_PERSISTENT_ACLS) != 0 end - def change_sid(old_sid, new_sid, info, path) - if old_sid != new_sid - mode = get_mode(path) - - string_to_sid_ptr(new_sid) do |psid| - with_privilege(SE_RESTORE_NAME) do - open_file(path, WRITE_OWNER) do |handle| - set_security_info(handle, info, psid) - end - end - end - - # rebuild dacl now that sid has changed - set_mode(mode, path) - end - end - - def get_sid(info, path) - with_privilege(SE_BACKUP_NAME) do - open_file(path, READ_CONTROL) do |handle| - get_security_info(handle, info) - end - end - end - def get_attributes(path) attributes = GetFileAttributes(path) @@ -222,6 +208,10 @@ module Puppet::Util::Windows::Security (FILE_GENERIC_EXECUTE & ~FILE_READ_ATTRIBUTES) => S_IXOTH } + def get_aces_for_path_by_sid(path, sid) + get_security_descriptor(path).dacl.select { |ace| ace.sid == sid } + end + # Get the mode of the object referenced by +path+. The returned # integer value represents the POSIX-style read, write, and execute # modes for the user, group, and other classes, e.g. 0640. Any user @@ -231,58 +221,61 @@ module Puppet::Util::Windows::Security def get_mode(path) return unless supports_acl?(path) - owner_sid = get_owner(path) - group_sid = get_group(path) well_known_world_sid = Win32::Security::SID::Everyone well_known_nobody_sid = Win32::Security::SID::Nobody + well_known_system_sid = Win32::Security::SID::LocalSystem - with_privilege(SE_BACKUP_NAME) do - open_file(path, READ_CONTROL) do |handle| - mode = 0 - - get_dacl(handle).each do |ace| - case ace[:sid] - when owner_sid - MASK_TO_MODE.each_pair do |k,v| - if (ace[:mask] & k) == k - mode |= (v << 6) - end - end - when group_sid - MASK_TO_MODE.each_pair do |k,v| - if (ace[:mask] & k) == k - mode |= (v << 3) - end - end - when well_known_world_sid - MASK_TO_MODE.each_pair do |k,v| - if (ace[:mask] & k) == k - mode |= (v << 6) | (v << 3) | v - end - end - if File.directory?(path) and (ace[:mask] & (FILE_WRITE_DATA | FILE_EXECUTE | FILE_DELETE_CHILD)) == (FILE_WRITE_DATA | FILE_EXECUTE) - mode |= S_ISVTX; - end - when well_known_nobody_sid - if (ace[:mask] & FILE_APPEND_DATA).nonzero? - mode |= S_ISVTX - end - else - #puts "Warning, unable to map SID into POSIX mode: #{ace[:sid]}" - mode |= S_IEXTRA - end + mode = S_ISYSTEM_MISSING - # if owner and group the same, then user and group modes are the OR of both - if owner_sid == group_sid - mode |= ((mode & S_IRWXG) << 3) | ((mode & S_IRWXU) >> 3) - #puts "owner: #{group_sid}, 0x#{ace[:mask].to_s(16)}, #{mode.to_s(8)}" + sd = get_security_descriptor(path) + sd.dacl.each do |ace| + next if ace.inherit_only? + + case ace.sid + when sd.owner + MASK_TO_MODE.each_pair do |k,v| + if (ace.mask & k) == k + mode |= (v << 6) + end + end + when sd.group + MASK_TO_MODE.each_pair do |k,v| + if (ace.mask & k) == k + mode |= (v << 3) + end + end + when well_known_world_sid + MASK_TO_MODE.each_pair do |k,v| + if (ace.mask & k) == k + mode |= (v << 6) | (v << 3) | v end end + if File.directory?(path) && (ace.mask & (FILE_WRITE_DATA | FILE_EXECUTE | FILE_DELETE_CHILD)) == (FILE_WRITE_DATA | FILE_EXECUTE) + mode |= S_ISVTX; + end + when well_known_nobody_sid + if (ace.mask & FILE_APPEND_DATA).nonzero? + mode |= S_ISVTX + end + when well_known_system_sid + else + #puts "Warning, unable to map SID into POSIX mode: #{ace.sid}" + mode |= S_IEXTRA + end - #puts "get_mode: #{mode.to_s(8)}" - mode + if ace.sid == well_known_system_sid + mode &= ~S_ISYSTEM_MISSING + end + + # if owner and group the same, then user and group modes are the OR of both + if sd.owner == sd.group + mode |= ((mode & S_IRWXG) << 3) | ((mode & S_IRWXU) >> 3) + #puts "owner: #{sd.group}, 0x#{ace.mask.to_s(16)}, #{mode.to_s(8)}" end end + + #puts "get_mode: #{mode.to_s(8)}" + mode end MODE_TO_MASK = { @@ -305,15 +298,16 @@ module Puppet::Util::Windows::Security # privileges in their process token can change the mode for objects # that they do not have read and write access to. def set_mode(mode, path, protected = true) - owner_sid = get_owner(path) - group_sid = get_group(path) + sd = get_security_descriptor(path) well_known_world_sid = Win32::Security::SID::Everyone well_known_nobody_sid = Win32::Security::SID::Nobody + well_known_system_sid = Win32::Security::SID::LocalSystem owner_allow = STANDARD_RIGHTS_ALL | FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES group_allow = STANDARD_RIGHTS_READ | FILE_READ_ATTRIBUTES | SYNCHRONIZE other_allow = STANDARD_RIGHTS_READ | FILE_READ_ATTRIBUTES | SYNCHRONIZE nobody_allow = 0 + system_allow = 0 MODE_TO_MASK.each do |k,v| if ((mode >> 6) & k) == k @@ -331,22 +325,29 @@ module Puppet::Util::Windows::Security nobody_allow |= FILE_APPEND_DATA; end + # caller is NOT managing SYSTEM by using group or owner, so set to FULL + if ! [sd.owner, sd.group].include? well_known_system_sid + # we don't check S_ISYSTEM_MISSING bit, but automatically carry over existing SYSTEM perms + # by default set SYSTEM perms to full + system_allow = FILE_ALL_ACCESS + end + isdir = File.directory?(path) if isdir if (mode & (S_IWUSR | S_IXUSR)) == (S_IWUSR | S_IXUSR) owner_allow |= FILE_DELETE_CHILD end - if (mode & (S_IWGRP | S_IXGRP)) == (S_IWGRP | S_IXGRP) and (mode & S_ISVTX) == 0 + if (mode & (S_IWGRP | S_IXGRP)) == (S_IWGRP | S_IXGRP) && (mode & S_ISVTX) == 0 group_allow |= FILE_DELETE_CHILD end - if (mode & (S_IWOTH | S_IXOTH)) == (S_IWOTH | S_IXOTH) and (mode & S_ISVTX) == 0 + if (mode & (S_IWOTH | S_IXOTH)) == (S_IWOTH | S_IXOTH) && (mode & S_ISVTX) == 0 other_allow |= FILE_DELETE_CHILD end end # if owner and group the same, then map group permissions to the one owner ACE - isownergroup = owner_sid == group_sid + isownergroup = sd.owner == sd.group if isownergroup owner_allow |= group_allow end @@ -357,64 +358,37 @@ module Puppet::Util::Windows::Security remove_attributes(path, FILE_ATTRIBUTE_READONLY) end - set_acl(path, protected) do |acl| - #puts "ace: owner #{owner_sid}, mask 0x#{owner_allow.to_s(16)}" - add_access_allowed_ace(acl, owner_allow, owner_sid) - - unless isownergroup - #puts "ace: group #{group_sid}, mask 0x#{group_allow.to_s(16)}" - add_access_allowed_ace(acl, group_allow, group_sid) - end - - #puts "ace: other #{well_known_world_sid}, mask 0x#{other_allow.to_s(16)}" - add_access_allowed_ace(acl, other_allow, well_known_world_sid) + dacl = Puppet::Util::Windows::AccessControlList.new + dacl.allow(sd.owner, owner_allow) + unless isownergroup + dacl.allow(sd.group, group_allow) + end + dacl.allow(well_known_world_sid, other_allow) + dacl.allow(well_known_nobody_sid, nobody_allow) - #puts "ace: nobody #{well_known_nobody_sid}, mask 0x#{nobody_allow.to_s(16)}" - add_access_allowed_ace(acl, nobody_allow, well_known_nobody_sid) + # TODO: system should be first? + dacl.allow(well_known_system_sid, system_allow) - # add inherit-only aces for child dirs and files that are created within the dir - if isdir - inherit = INHERIT_ONLY_ACE | CONTAINER_INHERIT_ACE - add_access_allowed_ace(acl, owner_allow, Win32::Security::SID::CreatorOwner, inherit) - add_access_allowed_ace(acl, group_allow, Win32::Security::SID::CreatorGroup, inherit) + # add inherit-only aces for child dirs and files that are created within the dir + if isdir + inherit = INHERIT_ONLY_ACE | CONTAINER_INHERIT_ACE + dacl.allow(Win32::Security::SID::CreatorOwner, owner_allow, inherit) + dacl.allow(Win32::Security::SID::CreatorGroup, group_allow, inherit) - inherit = INHERIT_ONLY_ACE | OBJECT_INHERIT_ACE - add_access_allowed_ace(acl, owner_allow & ~FILE_EXECUTE, Win32::Security::SID::CreatorOwner, inherit) - add_access_allowed_ace(acl, group_allow & ~FILE_EXECUTE, Win32::Security::SID::CreatorGroup, inherit) - end + inherit = INHERIT_ONLY_ACE | OBJECT_INHERIT_ACE + dacl.allow(Win32::Security::SID::CreatorOwner, owner_allow & ~FILE_EXECUTE, inherit) + dacl.allow(Win32::Security::SID::CreatorGroup, group_allow & ~FILE_EXECUTE, inherit) end + new_sd = Puppet::Util::Windows::SecurityDescriptor.new(sd.owner, sd.group, dacl, protected) + set_security_descriptor(path, new_sd) + nil end - # setting DACL requires both READ_CONTROL and WRITE_DACL access rights, - # and their respective privileges, SE_BACKUP_NAME and SE_RESTORE_NAME. - def set_acl(path, protected = true) - with_privilege(SE_BACKUP_NAME) do - with_privilege(SE_RESTORE_NAME) do - open_file(path, READ_CONTROL | WRITE_DAC) do |handle| - acl = 0.chr * 1024 # This can be increased later as needed - - unless InitializeAcl(acl, acl.size, ACL_REVISION) - raise Puppet::Util::Windows::Error.new("Failed to initialize ACL") - end - - raise Puppet::Util::Windows::Error.new("Invalid DACL") unless IsValidAcl(acl) - - yield acl - - # protected means the object does not inherit aces from its parent - info = DACL_SECURITY_INFORMATION - info |= protected ? PROTECTED_DACL_SECURITY_INFORMATION : UNPROTECTED_DACL_SECURITY_INFORMATION - - # set the DACL - set_security_info(handle, info, acl) - end - end - end - end + def add_access_allowed_ace(acl, mask, sid, inherit = nil) + inherit ||= NO_INHERITANCE - def add_access_allowed_ace(acl, mask, sid, inherit = NO_INHERITANCE) string_to_sid_ptr(sid) do |sid_ptr| raise Puppet::Util::Windows::Error.new("Invalid SID") unless IsValidSid(sid_ptr) @@ -434,126 +408,68 @@ module Puppet::Util::Windows::Security end end - def get_dacl(handle) - get_dacl_ptr(handle) do |dacl_ptr| - # REMIND: need to handle NULL DACL - raise Puppet::Util::Windows::Error.new("Invalid DACL") unless IsValidAcl(dacl_ptr) - - # ACL structure, size and count are the important parts. The - # size includes both the ACL structure and all the ACEs. + def parse_dacl(dacl_ptr) + # REMIND: need to handle NULL DACL + raise Puppet::Util::Windows::Error.new("Invalid DACL") unless IsValidAcl(dacl_ptr) + + # ACL structure, size and count are the important parts. The + # size includes both the ACL structure and all the ACEs. + # + # BYTE AclRevision + # BYTE Padding1 + # WORD AclSize + # WORD AceCount + # WORD Padding2 + acl_buf = 0.chr * 8 + memcpy(acl_buf, dacl_ptr, acl_buf.size) + ace_count = acl_buf.unpack('CCSSS')[3] + + dacl = Puppet::Util::Windows::AccessControlList.new + + # deny all + return dacl if ace_count == 0 + + 0.upto(ace_count - 1) do |i| + ace_ptr = [0].pack('L') + + next unless GetAce(dacl_ptr, i, ace_ptr) + + # ACE structures vary depending on the type. All structures + # begin with an ACE header, which specifies the type, flags + # and size of what follows. We are only concerned with + # ACCESS_ALLOWED_ACE and ACCESS_DENIED_ACEs, which have the + # same structure: # - # BYTE AclRevision - # BYTE Padding1 - # WORD AclSize - # WORD AceCount - # WORD Padding2 - acl_buf = 0.chr * 8 - memcpy(acl_buf, dacl_ptr, acl_buf.size) - ace_count = acl_buf.unpack('CCSSS')[3] - - dacl = [] - - # deny all - return dacl if ace_count == 0 - - 0.upto(ace_count - 1) do |i| - ace_ptr = [0].pack('L') - - next unless GetAce(dacl_ptr, i, ace_ptr) - - # ACE structures vary depending on the type. All structures - # begin with an ACE header, which specifies the type, flags - # and size of what follows. We are only concerned with - # ACCESS_ALLOWED_ACE and ACCESS_DENIED_ACEs, which have the - # same structure: - # - # BYTE C AceType - # BYTE C AceFlags - # WORD S AceSize - # DWORD L ACCESS_MASK - # DWORD L Sid - # .. ... - # DWORD L Sid - - ace_buf = 0.chr * 8 - memcpy(ace_buf, ace_ptr.unpack('L')[0], ace_buf.size) - - ace_type, ace_flags, size, mask = ace_buf.unpack('CCSL') - - # skip aces that only serve to propagate inheritance - next if (ace_flags & INHERIT_ONLY_ACE).nonzero? - - case ace_type - when ACCESS_ALLOWED_ACE_TYPE - sid_ptr = ace_ptr.unpack('L')[0] + 8 # address of ace_ptr->SidStart - raise Puppet::Util::Windows::Error.new("Failed to read DACL, invalid SID") unless IsValidSid(sid_ptr) - sid = sid_ptr_to_string(sid_ptr) - dacl << {:sid => sid, :type => ace_type, :mask => mask} - else - Puppet.warning "Unsupported access control entry type: 0x#{ace_type.to_s(16)}" - end + # BYTE C AceType + # BYTE C AceFlags + # WORD S AceSize + # DWORD L ACCESS_MASK + # DWORD L Sid + # .. ... + # DWORD L Sid + + ace_buf = 0.chr * 8 + memcpy(ace_buf, ace_ptr.unpack('L')[0], ace_buf.size) + + ace_type, ace_flags, size, mask = ace_buf.unpack('CCSL') + + case ace_type + when ACCESS_ALLOWED_ACE_TYPE + sid_ptr = ace_ptr.unpack('L')[0] + 8 # address of ace_ptr->SidStart + raise Puppet::Util::Windows::Error.new("Failed to read DACL, invalid SID") unless IsValidSid(sid_ptr) + sid = sid_ptr_to_string(sid_ptr) + dacl.allow(sid, mask, ace_flags) + when ACCESS_DENIED_ACE_TYPE + sid_ptr = ace_ptr.unpack('L')[0] + 8 # address of ace_ptr->SidStart + raise Puppet::Util::Windows::Error.new("Failed to read DACL, invalid SID") unless IsValidSid(sid_ptr) + sid = sid_ptr_to_string(sid_ptr) + dacl.deny(sid, mask, ace_flags) + else + Puppet.warning "Unsupported access control entry type: 0x#{ace_type.to_s(16)}" end - - dacl end - end - def get_dacl_ptr(handle) - dacl = [0].pack('L') - sd = [0].pack('L') - - rv = GetSecurityInfo( - handle, - SE_FILE_OBJECT, - DACL_SECURITY_INFORMATION, - nil, - nil, - dacl, #dacl - nil, #sacl - sd) #sec desc - raise Puppet::Util::Windows::Error.new("Failed to get DACL") unless rv == ERROR_SUCCESS - begin - yield dacl.unpack('L')[0] - ensure - LocalFree(sd.unpack('L')[0]) - end - end - - # Set the security info on the specified handle. - def set_security_info(handle, info, ptr) - rv = SetSecurityInfo( - handle, - SE_FILE_OBJECT, - info, - (info & OWNER_SECURITY_INFORMATION) == OWNER_SECURITY_INFORMATION ? ptr : nil, - (info & GROUP_SECURITY_INFORMATION) == GROUP_SECURITY_INFORMATION ? ptr : nil, - (info & DACL_SECURITY_INFORMATION) == DACL_SECURITY_INFORMATION ? ptr : nil, - nil) - raise Puppet::Util::Windows::Error.new("Failed to set security information") unless rv == ERROR_SUCCESS - end - - # Get the SID string, e.g. "S-1-5-32-544", for the specified handle - # and type of information (owner, group). - def get_security_info(handle, info) - sid = [0].pack('L') - sd = [0].pack('L') - - rv = GetSecurityInfo( - handle, - SE_FILE_OBJECT, - info, # security info - info == OWNER_SECURITY_INFORMATION ? sid : nil, - info == GROUP_SECURITY_INFORMATION ? sid : nil, - nil, #dacl - nil, #sacl - sd) #sec desc - raise Puppet::Util::Windows::Error.new("Failed to get security information") unless rv == ERROR_SUCCESS - - begin - return sid_ptr_to_string(sid.unpack('L')[0]) - ensure - LocalFree(sd.unpack('L')[0]) - end + dacl end # Open an existing file with the specified access mode, and execute a @@ -565,7 +481,7 @@ module Puppet::Util::Windows::Security FILE_SHARE_READ | FILE_SHARE_WRITE, 0, # security_attributes OPEN_EXISTING, - FILE_FLAG_BACKUP_SEMANTICS, + FILE_FLAG_OPEN_REPARSE_POINT | FILE_FLAG_BACKUP_SEMANTICS, 0) # template raise Puppet::Util::Windows::Error.new("Failed to open '#{path}'") if handle == INVALID_HANDLE_VALUE begin @@ -620,4 +536,114 @@ module Puppet::Util::Windows::Security CloseHandle(token) end end + + def get_security_descriptor(path) + sd = nil + + with_privilege(SE_BACKUP_NAME) do + open_file(path, READ_CONTROL) do |handle| + owner_sid = [0].pack('L') + group_sid = [0].pack('L') + dacl = [0].pack('L') + ppsd = [0].pack('L') + + rv = GetSecurityInfo( + handle, + SE_FILE_OBJECT, + OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION, + owner_sid, + group_sid, + dacl, + nil, #sacl + ppsd) #sec desc + raise Puppet::Util::Windows::Error.new("Failed to get security information") unless rv == ERROR_SUCCESS + + begin + owner = sid_ptr_to_string(owner_sid.unpack('L')[0]) + group = sid_ptr_to_string(group_sid.unpack('L')[0]) + + control = FFI::MemoryPointer.new(:uint16, 1) + revision = FFI::MemoryPointer.new(:uint32, 1) + ffsd = FFI::Pointer.new(ppsd.unpack('L')[0]) + + if ! API.get_security_descriptor_control(ffsd, control, revision) + raise Puppet::Util::Windows::Error.new("Failed to get security descriptor control") + end + + protect = (control.read_uint16 & SE_DACL_PROTECTED) == SE_DACL_PROTECTED + + dacl = parse_dacl(dacl.unpack('L')[0]) + sd = Puppet::Util::Windows::SecurityDescriptor.new(owner, group, dacl, protect) + ensure + LocalFree(ppsd.unpack('L')[0]) + end + end + end + + sd + end + + # setting DACL requires both READ_CONTROL and WRITE_DACL access rights, + # and their respective privileges, SE_BACKUP_NAME and SE_RESTORE_NAME. + def set_security_descriptor(path, sd) + # REMIND: FFI + acl = 0.chr * 1024 # This can be increased later as neede + unless InitializeAcl(acl, acl.size, ACL_REVISION) + raise Puppet::Util::Windows::Error.new("Failed to initialize ACL") + end + + raise Puppet::Util::Windows::Error.new("Invalid DACL") unless IsValidAcl(acl) + + with_privilege(SE_BACKUP_NAME) do + with_privilege(SE_RESTORE_NAME) do + open_file(path, READ_CONTROL | WRITE_DAC | WRITE_OWNER) do |handle| + string_to_sid_ptr(sd.owner) do |ownersid| + string_to_sid_ptr(sd.group) do |groupsid| + sd.dacl.each do |ace| + case ace.type + when ACCESS_ALLOWED_ACE_TYPE + #puts "ace: allow, sid #{sid_to_name(ace.sid)}, mask 0x#{ace.mask.to_s(16)}" + add_access_allowed_ace(acl, ace.mask, ace.sid, ace.flags) + when ACCESS_DENIED_ACE_TYPE + #puts "ace: deny, sid #{sid_to_name(ace.sid)}, mask 0x#{ace.mask.to_s(16)}" + add_access_denied_ace(acl, ace.mask, ace.sid) + else + raise "We should never get here" + # TODO: this should have been a warning in an earlier commit + end + end + + # protected means the object does not inherit aces from its parent + flags = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION + flags |= sd.protect ? PROTECTED_DACL_SECURITY_INFORMATION : UNPROTECTED_DACL_SECURITY_INFORMATION + + rv = SetSecurityInfo(handle, + SE_FILE_OBJECT, + flags, + ownersid, + groupsid, + acl, + nil) + raise Puppet::Util::Windows::Error.new("Failed to set security information") unless rv == ERROR_SUCCESS + end + end + end + end + end + end + + module API + extend FFI::Library + ffi_lib 'kernel32' + ffi_convention :stdcall + + # typedef WORD SECURITY_DESCRIPTOR_CONTROL, *PSECURITY_DESCRIPTOR_CONTROL; + # BOOL WINAPI GetSecurityDescriptorControl( + # _In_ PSECURITY_DESCRIPTOR pSecurityDescriptor, + # _Out_ PSECURITY_DESCRIPTOR_CONTROL pControl, + # _Out_ LPDWORD lpdwRevision + # ); + ffi_lib :advapi32 + attach_function :get_security_descriptor_control, :GetSecurityDescriptorControl, [:pointer, :pointer, :pointer], :bool + end end diff --git a/lib/puppet/util/windows/security_descriptor.rb b/lib/puppet/util/windows/security_descriptor.rb new file mode 100644 index 000000000..9c95cee6d --- /dev/null +++ b/lib/puppet/util/windows/security_descriptor.rb @@ -0,0 +1,62 @@ +# Windows Security Descriptor +# +# Represents a security descriptor that can be applied to any Windows securable +# object, e.g. file, registry key, service, etc. It consists of an owner, group, +# flags, DACL, and SACL. The SACL is not currently supported, though it has the +# same layout as a DACL. +# +# @see http://msdn.microsoft.com/en-us/library/windows/desktop/aa379563(v=vs.85).aspx +# @api private +class Puppet::Util::Windows::SecurityDescriptor + require 'puppet/util/windows/security' + include Puppet::Util::Windows::SID + + attr_reader :owner, :group, :dacl + attr_accessor :protect + + # Construct a security descriptor + # + # @param owner [String] The SID of the owner, e.g. 'S-1-5-18' + # @param group [String] The SID of the group + # @param dacl [AccessControlList] The ACL specifying the rights granted to + # each user for accessing the object that the security descriptor refers to. + # @param protect [Boolean] If true, then inheritable access control + # entries will be blocked, and not applied to the object. + def initialize(owner, group, dacl, protect = false) + @owner = owner + @group = group + @dacl = dacl + @protect = protect + end + + # Set the owner. Non-inherited access control entries assigned to the + # current owner will be assigned to the new owner. + # + # @param new_owner [String] The SID of the new owner, e.g. 'S-1-5-18' + def owner=(new_owner) + if @owner != new_owner + @dacl.reassign!(@owner, new_owner) + @owner = new_owner + end + end + + # Set the group. Non-inherited access control entries assigned to the + # current group will be assigned to the new group. + # + # @param new_group [String] The SID of the new group, e.g. 'S-1-0-0' + def group=(new_group) + if @group != new_group + @dacl.reassign!(@group, new_group) + @group = new_group + end + end + + def inspect + str = sid_to_name(owner) + str << "\n" + str << sid_to_name(group) + str << "\n" + str << @dacl.inspect + str + end +end diff --git a/lib/puppet/util/windows/sid.rb b/lib/puppet/util/windows/sid.rb index cd321eacb..90b48d933 100644 --- a/lib/puppet/util/windows/sid.rb +++ b/lib/puppet/util/windows/sid.rb @@ -20,17 +20,39 @@ module Puppet::Util::Windows # 'BUILTIN\Administrators', or 'S-1-5-32-544', and will return the # SID. Returns nil if the account doesn't exist. def name_to_sid(name) + sid = name_to_sid_object(name) + + sid ? sid.to_s : nil + end + + # Convert an account name, e.g. 'Administrators' into a SID object, + # e.g. 'S-1-5-32-544'. The name can be specified as 'Administrators', + # 'BUILTIN\Administrators', or 'S-1-5-32-544', and will return the + # SID object. Returns nil if the account doesn't exist. + def name_to_sid_object(name) # Apparently, we accept a symbol.. - name = name.to_s if name + name = name.to_s.strip if name - # if it's in SID string form, return it, otherwise, lookup sid - is_sid = Win32::Security::SID.string_to_sid(name) rescue nil + # if it's in SID string form, convert to user + parsed_sid = Win32::Security::SID.string_to_sid(name) rescue nil - is_sid ? name : Win32::Security::SID.new(name).to_s + parsed_sid ? Win32::Security::SID.new(parsed_sid) : Win32::Security::SID.new(name) rescue nil end + # Converts an octet string array of bytes to a SID object, + # e.g. [1, 1, 0, 0, 0, 0, 0, 5, 18, 0, 0, 0] is the representation for + # S-1-5-18, the local 'SYSTEM' account. + # Raises an Error for nil or non-array input. + def octet_string_to_sid_object(bytes) + if !bytes || !bytes.respond_to?('pack') || bytes.empty? + raise Puppet::Util::Windows::Error.new("Octet string must be an array of bytes") + end + + Win32::Security::SID.new(bytes.pack('C*')) + end + # Convert a SID string, e.g. "S-1-5-32-544" to a name, # e.g. 'BUILTIN\Administrators'. Returns nil if an account # for that SID does not exist. diff --git a/lib/puppet/util/yaml.rb b/lib/puppet/util/yaml.rb index d0c37ae77..246080843 100644 --- a/lib/puppet/util/yaml.rb +++ b/lib/puppet/util/yaml.rb @@ -9,8 +9,9 @@ module Puppet::Util::Yaml class YamlLoadError < Puppet::Error; end - def self.load_file(filename) - YAML.load_file(filename) + def self.load_file(filename, default_value = false) + yaml = YAML.load_file(filename) + yaml || default_value rescue *YamlLoadExceptions => detail raise YamlLoadError.new(detail.message, detail) end diff --git a/lib/puppet/version.rb b/lib/puppet/version.rb index 9f9f738be..b416a864b 100644 --- a/lib/puppet/version.rb +++ b/lib/puppet/version.rb @@ -7,7 +7,7 @@ module Puppet - PUPPETVERSION = '3.3.1' + PUPPETVERSION = '3.4.0' ## # version is a public API method intended to always provide a fast and @@ -81,7 +81,7 @@ module Puppet # @return [String] for example: "1.6.14-6-gea42046" or nil if the VERSION # file does not exist. def self.read_version_file(path) - if File.exists?(path) + if File.exist?(path) File.read(path).chomp end end |
