diff options
| author | Stig Sandbeck Mathisen <ssm@debian.org> | 2014-10-24 13:27:50 +0200 |
|---|---|---|
| committer | Stig Sandbeck Mathisen <ssm@debian.org> | 2014-10-24 13:27:50 +0200 |
| commit | deaf3b4969d6518166fd08a10a387c45a4fc58ec (patch) | |
| tree | 1fc59395cdea4c292f53e377338649c1ea369703 | |
| parent | 52dd92b16375a2ab1242e3fadb42567abd798cd6 (diff) | |
| parent | 652bc05e123a78ffe7b34f9b9f674e6e04be6ee5 (diff) | |
| download | puppet-deaf3b4969d6518166fd08a10a387c45a4fc58ec.tar.gz | |
Imported Upstream version 3.7.2upstream/3.7.2
59 files changed, 968 insertions, 229 deletions
diff --git a/ext/build_defaults.yaml b/ext/build_defaults.yaml index d62e70555..95b378942 100644 --- a/ext/build_defaults.yaml +++ b/ext/build_defaults.yaml @@ -16,7 +16,7 @@ build_gem: TRUE build_dmg: TRUE build_msi: puppet_for_the_win: - ref: 'c61dda253b09bd855488f7b6dd7332fdf84d3039' + ref: 'f4f74b1c8d8792089d1d3328b01f5ff075771eb0' repo: 'git://github.com/puppetlabs/puppet_for_the_win.git' facter: ref: 'refs/tags/2.2.0' @@ -26,8 +26,8 @@ build_msi: repo: 'git://github.com/puppetlabs/hiera.git' sys: ref: - x86: '890ad47c3b70de4e4956d2699b54d481d5a1b72e' - x64: '2820779a3f4281534a6792b0ab20b2cf213647dc' + x86: 'cbe94f4cafb0f78d587e8addcf723bc671af7cca' + x64: '417378f607340d211fbfef89a96e6639bef1bfb1' repo: 'git://github.com/puppetlabs/puppet-win32-ruby.git' apt_host: 'apt.puppetlabs.com' apt_repo_url: 'http://apt.puppetlabs.com' diff --git a/ext/debian/changelog b/ext/debian/changelog index f59ac3ba1..f34f69456 100644 --- a/ext/debian/changelog +++ b/ext/debian/changelog @@ -1,8 +1,8 @@ -puppet (3.7.1-1puppetlabs1) hardy lucid natty oneiric unstable sid squeeze wheezy precise; urgency=low +puppet (3.7.2-1puppetlabs1) hardy lucid natty oneiric unstable sid squeeze wheezy precise; urgency=low - * Update to version 3.7.1-1puppetlabs1 + * Update to version 3.7.2-1puppetlabs1 - -- Puppet Labs Release <info@puppetlabs.com> Mon, 15 Sep 2014 13:33:06 -0700 + -- Puppet Labs Release <info@puppetlabs.com> Tue, 21 Oct 2014 14:52:30 -0700 puppet (3.2.3-0.1rc0puppetlabs1) lucid unstable sid squeeze wheezy precise quantal raring; urgency=low diff --git a/ext/debian/control b/ext/debian/control index 02c1f8f42..3bdccb683 100644 --- a/ext/debian/control +++ b/ext/debian/control @@ -60,6 +60,7 @@ Architecture: all Depends: ${misc:Depends}, ruby | ruby-interpreter, puppet-common (= ${binary:Version}), facter (>= 1.7.0), lsb-base Breaks: puppet (<< 0.24.7-1), puppetmaster (<< 2.6.1~rc2-1) Replaces: puppetmaster (<< 2.6.1~rc2-1) +Conflicts: puppet-common (<< 3.3.0-1puppetlabs1) Suggests: apache2 | nginx, puppet-el, vim-puppet, stompserver, ruby-stomp | libstomp-ruby1.8, rdoc, ruby-ldap | libldap-ruby1.8, puppetdb-terminus Description: Puppet master common scripts @@ -81,6 +82,7 @@ Package: puppetmaster Architecture: all Depends: ${misc:Depends}, ruby | ruby-interpreter, puppetmaster-common (= ${source:Version}), facter (>= 1.7.0), lsb-base Breaks: puppet (<< 0.24.7-1) +Conflicts: puppet (<< 3.3.0-1puppetlabs1) Suggests: apache2 | nginx, puppet-el, vim-puppet, stompserver, ruby-stomp | libstomp-ruby1.8, rdoc, ruby-ldap | libldap-ruby1.8, puppetdb-terminus Description: Centralized configuration management - master startup and compatibility scripts diff --git a/ext/ips/puppet.p5m b/ext/ips/puppet.p5m index fc3c204b3..f1fab5c2e 100644 --- a/ext/ips/puppet.p5m +++ b/ext/ips/puppet.p5m @@ -1,6 +1,6 @@ -set name=pkg.fmri value=pkg://puppetlabs.com/system/management/@3.7.1,13.3.0-0 +set name=pkg.fmri value=pkg://puppetlabs.com/system/management/@3.7.2,13.4.0-0 set name=pkg.summary value="Puppet, an automated configuration management tool" -set name=pkg.human-version value="3.7.1" +set name=pkg.human-version value="3.7.2" set name=pkg.description value="Puppet, an automated configuration management tool" set name=info.classification value="org.opensolaris.category.2008:System/Administration and Configuration" set name=org.opensolaris.consolidation value="puppet" diff --git a/ext/project_data.yaml b/ext/project_data.yaml index 292d8de26..3f0867dc0 100644 --- a/ext/project_data.yaml +++ b/ext/project_data.yaml @@ -33,7 +33,7 @@ gem_platform_dependencies: win32-eventlog: '~> 0.6.1' win32-process: '~> 0.7.4' win32-security: '~> 0.2.5' - win32-service: '~> 0.8.4' + win32-service: '~> 0.8.6' win32console: '1.3.2' minitar: '~> 0.5.4' x64-mingw32: @@ -43,7 +43,7 @@ gem_platform_dependencies: win32-eventlog: '~> 0.6.1' win32-process: '~> 0.7.4' win32-security: '~> 0.2.5' - win32-service: '~> 0.8.4' + win32-service: '~> 0.8.6' minitar: '~> 0.5.4' bundle_platforms: x86-mingw32: mingw diff --git a/ext/redhat/puppet.spec b/ext/redhat/puppet.spec index c027fcc5e..0887760a8 100644 --- a/ext/redhat/puppet.spec +++ b/ext/redhat/puppet.spec @@ -16,8 +16,8 @@ %endif # VERSION is subbed out during rake srpm process -%global realversion 3.7.1 -%global rpmversion 3.7.1 +%global realversion 3.7.2 +%global rpmversion 3.7.2 %global confdir ext/redhat %global pending_upgrade_path %{_localstatedir}/lib/rpm-state/puppet @@ -446,8 +446,8 @@ fi rm -rf %{buildroot} %changelog -* Mon Sep 15 2014 Puppet Labs Release <info@puppetlabs.com> - 3.7.1-1 -- Build for 3.7.1 +* Tue Oct 21 2014 Puppet Labs Release <info@puppetlabs.com> - 3.7.2-1 +- Build for 3.7.2 * Wed Oct 2 2013 Jason Antman <jason@jasonantman.com> - Move systemd service and unit file names back to "puppet" from erroneous "puppetagent" diff --git a/lib/puppet/application.rb b/lib/puppet/application.rb index 1d9611093..0e98d8adb 100644 --- a/lib/puppet/application.rb +++ b/lib/puppet/application.rb @@ -360,10 +360,7 @@ class Application # configured_environment_name = Puppet[:environment] if self.class.run_mode.name != :agent - configured_environment = Puppet.lookup(:environments).get(configured_environment_name) - if configured_environment.nil? - fail(Puppet::Environments::EnvironmentNotFound, configured_environment_name) - end + configured_environment = Puppet.lookup(:environments).get!(configured_environment_name) else configured_environment = Puppet::Node::Environment.remote(configured_environment_name) end diff --git a/lib/puppet/configurer.rb b/lib/puppet/configurer.rb index bfd4b518b..7206ee0ce 100644 --- a/lib/puppet/configurer.rb +++ b/lib/puppet/configurer.rb @@ -156,7 +156,9 @@ class Puppet::Configurer unless options[:catalog] begin if node = Puppet::Node.indirection.find(Puppet[:node_name_value], - :environment => @environment, :ignore_cache => true, :transaction_uuid => @transaction_uuid, + :environment => Puppet::Node::Environment.remote(@environment), + :ignore_cache => true, + :transaction_uuid => @transaction_uuid, :fail_on_404 => true) # If we have deserialized a node from a rest call, we want to set @@ -242,7 +244,7 @@ class Puppet::Configurer def send_report(report) puts report.summary if Puppet[:summarize] save_last_run_summary(report) - Puppet::Transaction::Report.indirection.save(report, nil, :environment => @environment) if Puppet[:report] + Puppet::Transaction::Report.indirection.save(report, nil, :environment => Puppet::Node::Environment.remote(@environment)) if Puppet[:report] rescue => detail Puppet.log_exception(detail, "Could not send report: #{detail}") end @@ -274,7 +276,7 @@ class Puppet::Configurer result = nil @duration = thinmark do result = Puppet::Resource::Catalog.indirection.find(Puppet[:node_name_value], - query_options.merge(:ignore_terminus => true, :environment => @environment)) + query_options.merge(:ignore_terminus => true, :environment => Puppet::Node::Environment.remote(@environment))) end Puppet.notice "Using cached catalog" result @@ -287,7 +289,7 @@ class Puppet::Configurer result = nil @duration = thinmark do result = Puppet::Resource::Catalog.indirection.find(Puppet[:node_name_value], - query_options.merge(:ignore_cache => true, :environment => @environment, :fail_on_404 => true)) + query_options.merge(:ignore_cache => true, :environment => Puppet::Node::Environment.remote(@environment), :fail_on_404 => true)) end result rescue SystemExit,NoMemoryError diff --git a/lib/puppet/environments.rb b/lib/puppet/environments.rb index 9f7ac5c31..ec7a12940 100644 --- a/lib/puppet/environments.rb +++ b/lib/puppet/environments.rb @@ -48,6 +48,14 @@ module Puppet::Environments # we are looking up # @return [Puppet::Setting::EnvironmentConf, nil] the configuration for the # requested environment, or nil if not found or no configuration is available + # + # @!macro [new] loader_get_or_fail + # Find a named environment or raise + # Puppet::Environments::EnvironmentNotFound when the named environment is + # does not exist. + # + # @param name [String,Symbol] The name of environment to find + # @return [Puppet::Node::Environment] the requested environment # A source of pre-defined environments. # @@ -76,6 +84,14 @@ module Puppet::Environments end end + # @!macro loader_get_or_fail + def get!(name) + if !environment = get(name) + raise EnvironmentNotFound, name + end + environment + end + # Returns a basic environment configuration object tied to the environment's # implementation values. Will not interpolate. # @@ -144,6 +160,14 @@ module Puppet::Environments Puppet::Node::Environment.new(name) end + # @note Because the Legacy system cannot list out all of its environments, + # this method will never fail and is only calling get directly. + # + # @!macro loader_get_or_fail + def get!(name) + get(name) + end + # @note we could return something here, but since legacy environments # are deprecated, there is no point. # @@ -187,11 +211,11 @@ module Puppet::Environments # @!macro loader_list def list valid_directories.collect do |envdir| - name = Puppet::FileSystem.basename_string(envdir) + name = Puppet::FileSystem.basename_string(envdir).intern setting_values = Puppet.settings.values(name, Puppet.settings.preferred_run_mode) env = Puppet::Node::Environment.create( - name.intern, + name, Puppet::Node::Environment.split_path(setting_values.interpolate(:modulepath)), setting_values.interpolate(:manifest), setting_values.interpolate(:config_version) @@ -206,6 +230,14 @@ module Puppet::Environments list.find { |env| env.name == name.intern } end + # @!macro loader_get_or_fail + def get!(name) + if !environment = get(name) + raise EnvironmentNotFound, name + end + environment + end + # @!macro loader_get_conf def get_conf(name) valid_directories.each do |envdir| @@ -259,6 +291,16 @@ module Puppet::Environments nil end + # @!macro loader_get_or_fail + def get!(name) + @loaders.each do |loader| + if env = loader.get(name) + return env + end + end + raise EnvironmentNotFound, name + end + # @!macro loader_get_conf def get_conf(name) @loaders.each do |loader| @@ -325,10 +367,12 @@ module Puppet::Environments end # Evicts the entry if it has expired - # + # Also clears caches in Settings that may prevent the entry from being updated def evict_if_expired(name) if (result = @cache[name]) && result.expired? @cache.delete(name) + + Puppet.settings.clear_environment_settings(name) end end diff --git a/lib/puppet/indirector/node/exec.rb b/lib/puppet/indirector/node/exec.rb index d4faf74f4..2535ffac5 100644 --- a/lib/puppet/indirector/node/exec.rb +++ b/lib/puppet/indirector/node/exec.rb @@ -21,7 +21,7 @@ class Puppet::Node::Exec < Puppet::Indirector::Exec # Set the requested environment if it wasn't overridden # If we don't do this it gets set to the local default - result[:environment] ||= request.environment.name + result[:environment] ||= request.environment create_node(request.key, result) end diff --git a/lib/puppet/indirector/request.rb b/lib/puppet/indirector/request.rb index 04c55f9a3..a156f9de5 100644 --- a/lib/puppet/indirector/request.rb +++ b/lib/puppet/indirector/request.rb @@ -95,8 +95,7 @@ class Puppet::Indirector::Request elsif (current_environment = Puppet.lookup(:current_environment)).name == env current_environment else - Puppet.lookup(:environments).get(env) || - raise(Puppet::Environments::EnvironmentNotFound, env) + Puppet.lookup(:environments).get!(env) end end diff --git a/lib/puppet/module.rb b/lib/puppet/module.rb index 3a3435c35..deecfd45b 100644 --- a/lib/puppet/module.rb +++ b/lib/puppet/module.rb @@ -29,7 +29,7 @@ class Puppet::Module def self.find(modname, environment = nil) return nil unless modname # Unless a specific environment is given, use the current environment - env = environment ? Puppet.lookup(:environments).get(environment) : Puppet.lookup(:current_environment) + env = environment ? Puppet.lookup(:environments).get!(environment) : Puppet.lookup(:current_environment) env.module(modname) end diff --git a/lib/puppet/module_tool.rb b/lib/puppet/module_tool.rb index 8f462f6d8..984947d33 100644 --- a/lib/puppet/module_tool.rb +++ b/lib/puppet/module_tool.rb @@ -151,7 +151,7 @@ module Puppet elsif options[:environment] # This use of looking up an environment is correct since it honours # a reguest to get a particular environment via environment name. - Puppet.lookup(:environments).get(options[:environment]) + Puppet.lookup(:environments).get!(options[:environment]) else Puppet.lookup(:current_environment) end diff --git a/lib/puppet/network/http/webrick.rb b/lib/puppet/network/http/webrick.rb index 820d4556c..aa4f38359 100644 --- a/lib/puppet/network/http/webrick.rb +++ b/lib/puppet/network/http/webrick.rb @@ -8,18 +8,15 @@ require 'puppet/ssl/certificate_revocation_list' require 'puppet/ssl/configuration' class Puppet::Network::HTTP::WEBrick + CIPHERS = "EDH+CAMELLIA:EDH+aRSA:EECDH+aRSA+AESGCM:EECDH+aRSA+SHA384:EECDH+aRSA+SHA256:EECDH:+CAMELLIA256:+AES256:+CAMELLIA128:+AES128:+SSLv3:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!DSS:!RC4:!SEED:!IDEA:!ECDSA:kEDH:CAMELLIA256-SHA:AES256-SHA:CAMELLIA128-SHA:AES128-SHA" + def initialize @listening = false end def listen(address, port) - arguments = {:BindAddress => address, :Port => port, :DoNotReverseLookup => true} - arguments.merge!(setup_logger) - arguments.merge!(setup_ssl) + @server = create_server(address, port) - BasicSocket.do_not_reverse_lookup = true - - @server = WEBrick::HTTPServer.new(arguments) @server.listeners.each { |l| l.start_immediately = false } @server.mount('/', Puppet::Network::HTTP::WEBrickREST) @@ -55,6 +52,19 @@ class Puppet::Network::HTTP::WEBrick @thread.join end + # @api private + def create_server(address, port) + arguments = {:BindAddress => address, :Port => port, :DoNotReverseLookup => true} + arguments.merge!(setup_logger) + arguments.merge!(setup_ssl) + + BasicSocket.do_not_reverse_lookup = true + + server = WEBrick::HTTPServer.new(arguments) + server.ssl_context.ciphers = CIPHERS + server + end + # Configure our http log file. def setup_logger # Make sure the settings are all ready for us. @@ -96,7 +106,7 @@ class Puppet::Network::HTTP::WEBrick results[:SSLCertificate] = host.certificate.content results[:SSLStartImmediately] = true results[:SSLEnable] = true - results[:SSLOptions] = OpenSSL::SSL::OP_NO_SSLv2 + results[:SSLOptions] = OpenSSL::SSL::OP_NO_SSLv2 | OpenSSL::SSL::OP_NO_SSLv3 raise Puppet::Error, "Could not find CA certificate" unless Puppet::SSL::Certificate.indirection.find(Puppet::SSL::CA_NAME) diff --git a/lib/puppet/node.rb b/lib/puppet/node.rb index d61d49385..32f11168c 100644 --- a/lib/puppet/node.rb +++ b/lib/puppet/node.rb @@ -67,7 +67,7 @@ class Puppet::Node # for a node when it has not specified its environment # Tt will be used to establish what the current environment is. # - self.environment = Puppet.lookup(:environments).get(Puppet[:environment]) + self.environment = Puppet.lookup(:environments).get!(Puppet[:environment]) end @environment @@ -76,7 +76,7 @@ class Puppet::Node def environment=(env) if env.is_a?(String) or env.is_a?(Symbol) - @environment = Puppet.lookup(:environments).get(env) + @environment = Puppet.lookup(:environments).get!(env) else @environment = env end diff --git a/lib/puppet/parser/ast/pops_bridge.rb b/lib/puppet/parser/ast/pops_bridge.rb index 9c6a31744..22a687971 100644 --- a/lib/puppet/parser/ast/pops_bridge.rb +++ b/lib/puppet/parser/ast/pops_bridge.rb @@ -55,13 +55,6 @@ class Puppet::Parser::AST::PopsBridge end end - class NilAsUndefExpression < Expression - def evaluate(scope) - result = super - result.nil? ? :undef : result - end - end - # Bridges the top level "Program" produced by the pops parser. # Its main purpose is to give one point where all definitions are instantiated (actually defined since the # Puppet 3x terminology is somewhat misleading - the definitions are instantiated, but instances of the created types @@ -113,11 +106,8 @@ class Puppet::Parser::AST::PopsBridge def instantiate_Parameter(o) # 3x needs parameters as an array of `[name]` or `[name, value_expr]` - # One problem is that the parameter evaluation takes place in the wrong context in 3x (the caller's and - # can thus reference all sorts of information. Here the value expression is wrapped in an AST Bridge to a Pops - # expression since the Pops side can not control the evaluation if o.value - [o.name, NilAsUndefExpression.new(:value => o.value)] + [o.name, Expression.new(:value => o.value)] else [o.name] end diff --git a/lib/puppet/parser/compiler.rb b/lib/puppet/parser/compiler.rb index 5df2916fc..125b8e9f2 100644 --- a/lib/puppet/parser/compiler.rb +++ b/lib/puppet/parser/compiler.rb @@ -187,9 +187,8 @@ class Puppet::Parser::Compiler classes_without_params = @node.classes end - evaluate_classes(classes_without_params, @node_scope || topscope) - evaluate_classes(classes_with_params, @node_scope || topscope) + evaluate_classes(classes_without_params, @node_scope || topscope) end # Evaluate each specified class in turn. If there are any classes we can't diff --git a/lib/puppet/parser/resource.rb b/lib/puppet/parser/resource.rb index cde664a33..f1f8ccd00 100644 --- a/lib/puppet/parser/resource.rb +++ b/lib/puppet/parser/resource.rb @@ -162,12 +162,10 @@ class Puppet::Parser::Resource < Puppet::Resource # if we ever receive a parameter named 'tag', set # the resource tags with its value. def set_parameter(param, value = nil) - if ! value.nil? + if ! param.is_a?(Puppet::Parser::Resource::Param) param = Puppet::Parser::Resource::Param.new( :name => param, :value => value, :source => self.source ) - elsif ! param.is_a?(Puppet::Parser::Resource::Param) - raise ArgumentError, "Received incomplete information - no value provided for parameter #{param}" end tag(*param.value) if param.name == :tag diff --git a/lib/puppet/parser/resource/param.rb b/lib/puppet/parser/resource/param.rb index 423ae65f1..cb81043ba 100644 --- a/lib/puppet/parser/resource/param.rb +++ b/lib/puppet/parser/resource/param.rb @@ -11,7 +11,7 @@ class Puppet::Parser::Resource::Param def initialize(hash) set_options(hash) - requiredopts(:name, :value) + requiredopts(:name) @name = @name.intern end diff --git a/lib/puppet/parser/type_loader.rb b/lib/puppet/parser/type_loader.rb index 1357f948d..2f6c77257 100644 --- a/lib/puppet/parser/type_loader.rb +++ b/lib/puppet/parser/type_loader.rb @@ -55,7 +55,7 @@ class Puppet::Parser::TypeLoader def environment=(env) if env.is_a?(String) or env.is_a?(Symbol) - @environment = Puppet.lookup(:environments).get(env) + @environment = Puppet.lookup(:environments).get!(env) else @environment = env end diff --git a/lib/puppet/pops/evaluator/access_operator.rb b/lib/puppet/pops/evaluator/access_operator.rb index e849a2c4e..623fb1a60 100644 --- a/lib/puppet/pops/evaluator/access_operator.rb +++ b/lib/puppet/pops/evaluator/access_operator.rb @@ -232,7 +232,7 @@ class Puppet::Pops::Evaluator::AccessOperator when :default 'Default' else - actual.class.name + Puppet::Pops::Types::TypeCalculator.generalize!(Puppet::Pops::Types::TypeCalculator.infer(actual)).to_s end end @@ -462,7 +462,7 @@ class Puppet::Pops::Evaluator::AccessOperator else # blame given left expression if it defined the type, else the first given key expression blame = o.type_name.nil? ? @semantic.keys[0] : @semantic.left_expr - fail(Puppet::Pops::Issues::ILLEGAL_RESOURCE_SPECIALIZATION, blame, {:actual => type_name.class}) + fail(Puppet::Pops::Issues::ILLEGAL_RESOURCE_SPECIALIZATION, blame, {:actual => bad_key_type_name(type_name)}) end # type name must conform @@ -508,18 +508,10 @@ class Puppet::Pops::Evaluator::AccessOperator keys = [:no_title] if keys.size < 1 # if there was only a type_name and it was consumed result = keys.each_with_index.map do |t, i| unless t.is_a?(String) || t == :no_title - type_to_report = case t - when nil - 'Undef' - when :default - 'Default' - else - t.class.name - end index = keys_orig_size != keys.size ? i+1 : i fail(Puppet::Pops::Issues::BAD_TYPE_SPECIALIZATION, @semantic.keys[index], { :type => o, - :message => "Cannot use #{type_to_report} where String is expected" + :message => "Cannot use #{bad_key_type_name(t)} where a resource title String is expected" }) end diff --git a/lib/puppet/pops/evaluator/evaluator_impl.rb b/lib/puppet/pops/evaluator/evaluator_impl.rb index 02098d674..94e7a2a8b 100644 --- a/lib/puppet/pops/evaluator/evaluator_impl.rb +++ b/lib/puppet/pops/evaluator/evaluator_impl.rb @@ -706,7 +706,7 @@ class Puppet::Pops::Evaluator::EvaluatorImpl if param_memo.include? p.name fail(Puppet::Pops::Issues::DUPLICATE_ATTRIBUTE, o, {:attribute => p.name}) end - param_memo[p.name] = p + param_memo[p.name] = p end param_memo end diff --git a/lib/puppet/pops/evaluator/runtime3_support.rb b/lib/puppet/pops/evaluator/runtime3_support.rb index 127c7d6f5..a86228d33 100644 --- a/lib/puppet/pops/evaluator/runtime3_support.rb +++ b/lib/puppet/pops/evaluator/runtime3_support.rb @@ -255,8 +255,7 @@ module Puppet::Pops::Evaluator::Runtime3Support file, line = extract_file_line(o) Puppet::Parser::Resource::Param.new( :name => name, - # Here we must convert nil values to :undef for the 3x logic to work - :value => convert(value, scope, :undef), # converted to 3x since 4x supports additional objects / types + :value => convert(value, scope, nil), # converted to 3x since 4x supports additional objects / types :source => scope.source, :line => line, :file => file, :add => operator == :'+>' ) @@ -394,6 +393,7 @@ module Puppet::Pops::Evaluator::Runtime3Support # Returns true, if the given name is the name of a resource parameter. # def is_parameter_of_resource?(scope, resource, name) + return false unless name.is_a?(String) resource.valid_parameter?(name) end @@ -425,6 +425,7 @@ module Puppet::Pops::Evaluator::Runtime3Support def initialize @@convert_visitor ||= Puppet::Pops::Visitor.new(self, "convert", 2, 2) + @@convert2_visitor ||= Puppet::Pops::Visitor.new(self, "convert2", 2, 2) end # Converts 4x supported values to 3x values. This is required because @@ -436,35 +437,53 @@ module Puppet::Pops::Evaluator::Runtime3Support @@convert_visitor.visit_this_2(self, o, scope, undef_value) end + # Converts nested 4x supported values to 3x values. This is required because + # resources and other objects do not know about the new type system, and does not support + # regular expressions. Unfortunately this has to be done for array and hash as well. + # A complication is that catalog types needs to be resolved against the scope. + # + def convert2(o, scope, undef_value) + @@convert2_visitor.visit_this_2(self, o, scope, undef_value) + end + def convert_NilClass(o, scope, undef_value) undef_value end + def convert2_NilClass(o, scope, undef_value) + :undef + end + def convert_String(o, scope, undef_value) # although wasteful, needed because user code may mutate these strings in Resources o.frozen? ? o.dup : o end + alias convert2_String :convert_String def convert_Object(o, scope, undef_value) o end + alias :convert2_Object :convert_Object def convert_Array(o, scope, undef_value) - o.map {|x| convert(x, scope, undef_value) } + o.map {|x| convert2(x, scope, undef_value) } end + alias :convert2_Array :convert_Array def convert_Hash(o, scope, undef_value) result = {} - o.each {|k,v| result[convert(k, scope, undef_value)] = convert(v, scope, undef_value) } + o.each {|k,v| result[convert2(k, scope, undef_value)] = convert2(v, scope, undef_value) } result end + alias :convert2_Hash :convert_Hash def convert_Regexp(o, scope, undef_value) # Puppet 3x cannot handle parameter values that are reqular expressions. Turn into regexp string in # source form o.inspect end + alias :convert2_Regexp :convert_Regexp def convert_Symbol(o, scope, undef_value) case o @@ -476,9 +495,15 @@ module Puppet::Pops::Evaluator::Runtime3Support end end + # The :undef symbol should not be converted when nested in arrays or hashes + def convert2_Symbol(o, scope, undef_value) + o + end + def convert_PAnyType(o, scope, undef_value) o end + alias :convert2_PAnyType :convert_PAnyType def convert_PCatalogEntryType(o, scope, undef_value) # Since 4x does not support dynamic scoping, all names are absolute and can be @@ -489,6 +514,7 @@ module Puppet::Pops::Evaluator::Runtime3Support Puppet::Resource.new(*catalog_type_to_split_type_title(o)) end + alias :convert2_PCatalogEntryType :convert_PCatalogEntryType private diff --git a/lib/puppet/pops/model/factory.rb b/lib/puppet/pops/model/factory.rb index cf77d9185..a814a56b9 100644 --- a/lib/puppet/pops/model/factory.rb +++ b/lib/puppet/pops/model/factory.rb @@ -785,6 +785,14 @@ class Puppet::Pops::Model::Factory STATEMENT_CALLS[name] end + class ArgsToNonCallError < RuntimeError + attr_reader :args, :name_expr + def initialize(args, name_expr) + @args = args + @name_expr = name_expr + end + end + # Transforms an array of expressions containing literal name expressions to calls if followed by an # expression, or expression list. # @@ -793,7 +801,12 @@ class Puppet::Pops::Model::Factory expr = expr.current if expr.is_a?(Puppet::Pops::Model::Factory) name = memo[-1] if name.is_a?(Model::QualifiedName) && STATEMENT_CALLS[name.value] - the_call = Puppet::Pops::Model::Factory.CALL_NAMED(name, false, expr.is_a?(Array) ? expr : [expr]) + if expr.is_a?(Array) + expr = expr.reject {|e| e.is_a?(Puppet::Pops::Parser::LexerSupport::TokenValue) } + else + expr = [expr] + end + the_call = Puppet::Pops::Model::Factory.CALL_NAMED(name, false, expr) # last positioned is last arg if there are several record_position(the_call, name, expr.is_a?(Array) ? expr[-1] : expr) memo[-1] = the_call @@ -803,6 +816,8 @@ class Puppet::Pops::Model::Factory # an argument to the name to call transform above. expr.rval_required = true end + elsif expr.is_a?(Array) + raise ArgsToNonCallError.new(expr, name) else memo << expr if expr.is_a?(Model::CallNamedFunctionExpression) diff --git a/lib/puppet/pops/parser/egrammar.ra b/lib/puppet/pops/parser/egrammar.ra index 54b183a81..b9cc896ca 100644 --- a/lib/puppet/pops/parser/egrammar.ra +++ b/lib/puppet/pops/parser/egrammar.ra @@ -86,7 +86,7 @@ syntactic_statements # syntactic_statement : assignment =LOW { result = val[0] } - | syntactic_statement COMMA assignment =LOW { result = aryfy(val[0]).push val[2] } + | syntactic_statement COMMA assignment =LOW { result = aryfy(val[0]).push(val[1]).push(val[2]) } # Assignment (is right recursive since assignment is right associative) assignment diff --git a/lib/puppet/pops/parser/eparser.rb b/lib/puppet/pops/parser/eparser.rb index 26ff778fe..ef14487e8 100644 --- a/lib/puppet/pops/parser/eparser.rb +++ b/lib/puppet/pops/parser/eparser.rb @@ -1296,7 +1296,7 @@ module_eval(<<'.,.,', 'egrammar.ra', 87) module_eval(<<'.,.,', 'egrammar.ra', 88) def _reduce_9(val, _values, result) - result = aryfy(val[0]).push val[2] + result = aryfy(val[0]).push(val[1]).push(val[2]) result end .,., diff --git a/lib/puppet/pops/parser/parser_support.rb b/lib/puppet/pops/parser/parser_support.rb index cb1c83aa2..ee4f16938 100644 --- a/lib/puppet/pops/parser/parser_support.rb +++ b/lib/puppet/pops/parser/parser_support.rb @@ -163,7 +163,25 @@ class Puppet::Pops::Parser::Parser # expression, or expression list # def transform_calls(expressions) - Factory.transform_calls(expressions) + # Factory transform raises an error if a non qualified name is followed by an argument list + # since there is no way that that can be transformed back to sanity. This occurs in situations like this: + # + # $a = 10, notice hello + # + # where the "10, notice" forms an argument list. The parser builds an Array with the expressions and includes + # the comma tokens to enable the error to be reported against the first comma. + # + begin + Factory.transform_calls(expressions) + rescue Puppet::Pops::Model::Factory::ArgsToNonCallError => e + # e.args[1] is the first comma token in the list + # e.name_expr is the function name expression + if e.name_expr.is_a?(Puppet::Pops::Model::QualifiedName) + error(e.args[1], "attempt to pass argument list to the function '#{e.name_expr.value}' which cannot be called without parentheses") + else + error(e.args[1], "illegal comma separated argument list") + end + end end # Transforms a LEFT followed by the result of attribute_operations, this may be a call or an invalid sequence diff --git a/lib/puppet/pops/types/type_calculator.rb b/lib/puppet/pops/types/type_calculator.rb index 644d007cd..9db174132 100644 --- a/lib/puppet/pops/types/type_calculator.rb +++ b/lib/puppet/pops/types/type_calculator.rb @@ -453,12 +453,11 @@ class Puppet::Pops::Types::TypeCalculator end def instance_of_PNilType(t, o) - return o.nil? + o.nil? || o == :undef end def instance_of_POptionalType(t, o) - return true if (o.nil?) - instance_of(t.optional_type, o) + instance_of_PNilType(t, o) || instance_of(t.optional_type, o) end def instance_of_PVariantType(t, o) @@ -786,7 +785,6 @@ class Puppet::Pops::Types::TypeCalculator case o when :default Types::PDefaultType.new() - else infer_Object(o) end @@ -1151,15 +1149,20 @@ class Puppet::Pops::Types::TypeCalculator # @api private def assignable_PEnumType(t, t2) - return true if t == t2 || (t.values.empty? && (t2.is_a?(Types::PStringType) || t2.is_a?(Types::PEnumType))) + return true if t == t2 + if t.values.empty? + return true if t2.is_a?(Types::PStringType) || t2.is_a?(Types::PEnumType) || t2.is_a?(Types::PPatternType) + end case t2 when Types::PStringType # if the set of strings are all found in the set of enums - t2.values.all? { |s| t.values.any? { |e| e == s }} + !t2.values.empty?() && t2.values.all? { |s| t.values.any? { |e| e == s }} when Types::PVariantType t2.types.all? {|variant_t| assignable_PEnumType(t, variant_t) } when Types::PEnumType - t2.values.all? { |s| t.values.any? {|e| e == s }} + # empty means any enum + return true if t.values.empty? + !t2.values.empty? && t2.values.all? { |s| t.values.any? {|e| e == s }} else false end @@ -1184,7 +1187,7 @@ class Puppet::Pops::Types::TypeCalculator assignable_PIntegerType(size_t, @collection_default_size_t) when Types::PEnumType - if t2.values + if t2.values && !t2.values.empty? # true if all enum values are within range min, max = t2.values.map(&:size).minmax trange = from_to_ordered(size_t.from, size_t.to) @@ -1192,8 +1195,9 @@ class Puppet::Pops::Types::TypeCalculator # If t2 min and max are within the range of t trange[0] <= t2range[0] && trange[1] >= t2range[1] else - # no string can match this enum anyway since it does not accept anything - false + # enum represents all enums, and thus all strings, a sized constrained string can thus not + # be assigned any enum (unless it is max size). + assignable_PIntegerType(size_t, @collection_default_size_t) end else # no other type matches string @@ -1217,6 +1221,8 @@ class Puppet::Pops::Types::TypeCalculator values = t2.values when Types::PVariantType return t2.types.all? {|variant_t| assignable_PPatternType(t, variant_t) } + when Types::PPatternType + return t.patterns.empty? ? true : false else return false end @@ -1226,9 +1232,10 @@ class Puppet::Pops::Types::TypeCalculator # (There should really always be a pattern, but better safe than sorry). return t.patterns.empty? ? true : false end - # all strings in String/Enum type must match one of the patterns in Pattern type + # all strings in String/Enum type must match one of the patterns in Pattern type, + # or Pattern represents all Patterns == all Strings regexps = t.patterns.map {|p| p.regexp } - t2.values.all? { |v| regexps.any? {|re| re.match(v) } } + regexps.empty? || t2.values.all? { |v| regexps.any? {|re| re.match(v) } } end # @api private @@ -1539,8 +1546,6 @@ class Puppet::Pops::Types::TypeCalculator # translate to string, and skip Unit types types = t.param_types.types.map {|t2| string(t2) unless t2.class == Types::PUnitType }.compact - params_part= types.join(', ') - s = "Callable[" << types.join(', ') unless range.empty? (s << ', ') unless types.empty? diff --git a/lib/puppet/provider/package/pkg.rb b/lib/puppet/provider/package/pkg.rb index 4192dca85..db7c0a008 100644 --- a/lib/puppet/provider/package/pkg.rb +++ b/lib/puppet/provider/package/pkg.rb @@ -126,7 +126,18 @@ Puppet::Type.type(:package).provide :pkg, :parent => Puppet::Provider::Package d # http://defect.opensolaris.org/bz/show_bug.cgi?id=19159% # notes that we can't use -Ha for the same even though the manual page reads that way. def latest - lst = pkg(:list, "-Hn", @resource[:name]).split("\n").map{|l|self.class.parse_line(l)} + lines = pkg(:list, "-Hn", @resource[:name]).split("\n") + + # remove certificate expiration warnings from the output, but report them + # Note: we'd like to use select! here to modify the lines array and avoid + # the second select further down. But Solaris 11 comes with ruby 1.8.7 + # which doesn't support select!, so do this as two selects. + cert_warnings = lines.select { |line| line =~ /^Certificate/ } + if cert_warnings + Puppet.warning("pkg warning: #{cert_warnings}") + end + + lst = lines.select { |line| line !~ /^Certificate/ }.map { |line| self.class.parse_line(line) } # Now we know there is a newer version. But is that installable? (i.e are there any constraints?) # return the first known we find. The only way that is currently available is to do a dry run of diff --git a/lib/puppet/provider/scheduled_task/win32_taskscheduler.rb b/lib/puppet/provider/scheduled_task/win32_taskscheduler.rb index 91526d7fa..d00d3da6f 100644 --- a/lib/puppet/provider/scheduled_task/win32_taskscheduler.rb +++ b/lib/puppet/provider/scheduled_task/win32_taskscheduler.rb @@ -105,7 +105,6 @@ Puppet::Type.type(:scheduled_task).provide(:win32_taskscheduler) do @triggers << puppet_trigger end - @triggers = @triggers[0] if @triggers.length == 1 @triggers end @@ -235,7 +234,7 @@ Puppet::Type.type(:scheduled_task).provide(:win32_taskscheduler) do return false if current_trigger.has_key?('enabled') && !current_trigger['enabled'] desired = desired_trigger.dup - + desired['start_date'] ||= current_trigger['start_date'] if current_trigger.has_key?('start_date') desired['every'] ||= current_trigger['every'] if current_trigger.has_key?('every') desired['months'] ||= current_trigger['months'] if current_trigger.has_key?('months') desired['on'] ||= current_trigger['on'] if current_trigger.has_key?('on') @@ -255,13 +254,11 @@ Puppet::Type.type(:scheduled_task).provide(:win32_taskscheduler) do def dummy_time_trigger now = Time.now - { 'flags' => 0, 'random_minutes_interval' => 0, 'end_day' => 0, "end_year" => 0, - "trigger_type" => 0, "minutes_interval" => 0, "end_month" => 0, "minutes_duration" => 0, @@ -274,22 +271,16 @@ Puppet::Type.type(:scheduled_task).provide(:win32_taskscheduler) do } end - def translate_hash_to_trigger(puppet_trigger, user_provided_input=false) + def translate_hash_to_trigger(puppet_trigger) trigger = dummy_time_trigger - if user_provided_input - self.fail "'enabled' is read-only on scheduled_task triggers and should be removed ('enabled' is usually provided in puppet resource scheduled_task)." if puppet_trigger.has_key?('enabled') - self.fail "'index' is read-only on scheduled_task triggers and should be removed ('index' is usually provided in puppet resource scheduled_task)." if puppet_trigger.has_key?('index') - end - puppet_trigger.delete('index') - - if puppet_trigger.delete('enabled') == false + if puppet_trigger['enabled'] == false trigger['flags'] |= Win32::TaskScheduler::TASK_TRIGGER_FLAG_DISABLED else trigger['flags'] &= ~Win32::TaskScheduler::TASK_TRIGGER_FLAG_DISABLED end - extra_keys = puppet_trigger.keys.sort - ['schedule', 'start_date', 'start_time', 'every', 'months', 'on', 'which_occurrence', 'day_of_week'] + extra_keys = puppet_trigger.keys.sort - ['index', 'enabled', 'schedule', 'start_date', 'start_time', 'every', 'months', 'on', 'which_occurrence', 'day_of_week'] self.fail "Unknown trigger option(s): #{Puppet::Parameter.format_value_for_display(extra_keys)}" unless extra_keys.empty? self.fail "Must specify 'start_time' when defining a trigger" unless puppet_trigger['start_time'] @@ -361,9 +352,17 @@ Puppet::Type.type(:scheduled_task).provide(:win32_taskscheduler) do def validate_trigger(value) value = [value] unless value.is_a?(Array) - # translate_hash_to_trigger handles the same validation that we - # would be doing here at the individual trigger level. - value.each {|t| translate_hash_to_trigger(t, true)} + value.each do |t| + if t.has_key?('index') + self.fail "'index' is read-only on scheduled_task triggers and should be removed ('index' is usually provided in puppet resource scheduled_task)." + end + + if t.has_key?('enabled') + self.fail "'enabled' is read-only on scheduled_task triggers and should be removed ('enabled' is usually provided in puppet resource scheduled_task)." + end + + translate_hash_to_trigger(t) + end true end diff --git a/lib/puppet/provider/ssh_authorized_key/parsed.rb b/lib/puppet/provider/ssh_authorized_key/parsed.rb index 67403c9d6..a1c9ad4c7 100644 --- a/lib/puppet/provider/ssh_authorized_key/parsed.rb +++ b/lib/puppet/provider/ssh_authorized_key/parsed.rb @@ -22,6 +22,8 @@ Puppet::Type.type(:ssh_authorized_key).provide( h[:options] = Puppet::Type::Ssh_authorized_key::ProviderParsed.parse_options(h[:options]) if h[:options].is_a? String }, :pre_gen => proc { |h| + # if this name was generated, don't write it back to disk + h[:name] = "" if h[:unnamed] h[:options] = [] if h[:options].include?(:absent) h[:options] = h[:options].join(',') } @@ -85,5 +87,19 @@ Puppet::Type.type(:ssh_authorized_key).provide( end result end + + def self.prefetch_hook(records) + name_index = 0 + records.each do |record| + if record[:record_type] == :parsed && record[:name].empty? + record[:unnamed] = true + # Generate a unique ID for unnamed keys, in case they need purging. + # If you change this, you have to keep + # Puppet::Type::User#unknown_keys_in_file in sync! (PUP-3357) + record[:name] = "#{record[:target]}:unnamed-#{ name_index += 1 }" + Puppet.debug("generating name for on-disk ssh_authorized_key #{record[:key]}: #{record[:name]}") + end + end + end end diff --git a/lib/puppet/resource.rb b/lib/puppet/resource.rb index a5419512b..82dec4bb0 100644 --- a/lib/puppet/resource.rb +++ b/lib/puppet/resource.rb @@ -186,9 +186,6 @@ class Puppet::Resource @is_stage ||= @type.to_s.downcase == "stage" end - # Cache to reduce respond_to? lookups - @@nondeprecating_type = {} - # Construct a resource from data. # # Constructs a resource instance with the given `type` and `title`. Multiple @@ -242,12 +239,8 @@ class Puppet::Resource extract_parameters(params) end - if resource_type and ! @@nondeprecating_type[resource_type] - if resource_type.respond_to?(:deprecate_params) + if resource_type && resource_type.respond_to?(:deprecate_params) resource_type.deprecate_params(title, attributes[:parameters]) - else - @@nondeprecating_type[resource_type] = true - end end tag(self.type) diff --git a/lib/puppet/settings.rb b/lib/puppet/settings.rb index 499ee1502..a0f41f1c7 100644 --- a/lib/puppet/settings.rb +++ b/lib/puppet/settings.rb @@ -205,6 +205,23 @@ class Puppet::Settings end private :unsafe_clear + # Clears all cached settings for a particular environment to ensure + # that changes to environment.conf are reflected in the settings if + # the environment timeout has expired. + # + # param [String, Symbol] environment the name of environment to clear settings for + # + # @api private + def clear_environment_settings(environment) + + if environment.nil? + return + end + + @cache[environment.to_sym].clear + @values[environment.to_sym] = {} + end + # Clear @cache, @used and the Environment. # # Whenever an object is returned by Settings, a copy is stored in @cache. diff --git a/lib/puppet/type/user.rb b/lib/puppet/type/user.rb index b9b26295f..81ad39a91 100644 --- a/lib/puppet/type/user.rb +++ b/lib/puppet/type/user.rb @@ -670,10 +670,20 @@ module Puppet # representing the found keys def unknown_keys_in_file(keyfile) names = [] + name_index = 0 File.new(keyfile).each do |line| next unless line =~ Puppet::Type.type(:ssh_authorized_key).keyline_regex # the name is stored in the 4th capture of the regex - names << $4 + name = $4 + if name.empty? + key = $3.delete("\n") + # If no comment is specified for this key, generate a unique internal + # name. This uses the same rules as + # provider/ssh_authorized_key/parsed (PUP-3357) + name = "#{keyfile}:unnamed-#{name_index += 1}" + end + names << name + Puppet.debug "#{self.ref} parsed for purging Ssh_authorized_key[#{name}]" end names.map { |keyname| diff --git a/lib/puppet/util/autoload.rb b/lib/puppet/util/autoload.rb index 51ff94e1c..7173fe906 100644 --- a/lib/puppet/util/autoload.rb +++ b/lib/puppet/util/autoload.rb @@ -127,14 +127,18 @@ class Puppet::Util::Autoload # now we are accomplishing that by calling the # "app_defaults_initialized?" method on the main puppet Settings object. # --cprice 2012-03-16 - if Puppet.settings.app_defaults_initialized? && + if Puppet.settings.app_defaults_initialized? env ||= Puppet.lookup(:environments).get(Puppet[:environment]) - # if the app defaults have been initialized then it should be safe to access the module path setting. - $env_module_directories[env] ||= env.modulepath.collect do |dir| - Dir.entries(dir).reject { |f| f =~ /^\./ }.collect { |f| File.join(dir, f, "lib") } - end.flatten.find_all do |d| - FileTest.directory?(d) + if env + # if the app defaults have been initialized then it should be safe to access the module path setting. + $env_module_directories[env] ||= env.modulepath.collect do |dir| + Dir.entries(dir).reject { |f| f =~ /^\./ }.collect { |f| File.join(dir, f, "lib") } + end.flatten.find_all do |d| + FileTest.directory?(d) + end + else + [] end else # if we get here, the app defaults have not been initialized, so we basically use an empty module path. diff --git a/lib/puppet/util/monkey_patches.rb b/lib/puppet/util/monkey_patches.rb index f9110986e..f1c6856f4 100644 --- a/lib/puppet/util/monkey_patches.rb +++ b/lib/puppet/util/monkey_patches.rb @@ -171,9 +171,9 @@ end require 'openssl' class OpenSSL::SSL::SSLContext if DEFAULT_PARAMS[:options] - DEFAULT_PARAMS[:options] |= OpenSSL::SSL::OP_NO_SSLv2 + DEFAULT_PARAMS[:options] |= OpenSSL::SSL::OP_NO_SSLv2 | OpenSSL::SSL::OP_NO_SSLv3 else - DEFAULT_PARAMS[:options] = OpenSSL::SSL::OP_NO_SSLv2 + DEFAULT_PARAMS[:options] = OpenSSL::SSL::OP_NO_SSLv2 | OpenSSL::SSL::OP_NO_SSLv3 end DEFAULT_PARAMS[:ciphers] << ':!SSLv2' diff --git a/lib/puppet/version.rb b/lib/puppet/version.rb index 08ed42f26..0fe170d5a 100644 --- a/lib/puppet/version.rb +++ b/lib/puppet/version.rb @@ -7,7 +7,7 @@ module Puppet - PUPPETVERSION = '3.7.1' + PUPPETVERSION = '3.7.2' ## # version is a public API method intended to always provide a fast and diff --git a/spec/fixtures/unit/provider/package/pkg/dummy_solaris11.certificate_warning b/spec/fixtures/unit/provider/package/pkg/dummy_solaris11.certificate_warning new file mode 100644 index 000000000..83849be03 --- /dev/null +++ b/spec/fixtures/unit/provider/package/pkg/dummy_solaris11.certificate_warning @@ -0,0 +1,2 @@ +Certificate '/var/pkg/ssl/871b4ed0ade09926e6adf95f86bf17535f987684' for publisher 'solarisstudio', needed to access 'https://pkg.oracle.com/solarisstudio/release/', will expire in '29' days. +dummy 1.0.6-0.175.0.0.0.2.537 i-- diff --git a/spec/fixtures/unit/type/user/authorized_keys b/spec/fixtures/unit/type/user/authorized_keys index dd1807e56..d58c62008 100644 --- a/spec/fixtures/unit/type/user/authorized_keys +++ b/spec/fixtures/unit/type/user/authorized_keys @@ -3,3 +3,4 @@ ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDTXvM7AslzjNUYrPLiNVBsF5VnqL2RmqrkzscdVdHzVxvieNwmLGeUkg8EfXPiz7j5F/Lr0J8oItTCWzyN2KmM+DhUMjvP4AbELO/VYbnVrZICRiUNYSO3EN9/uapKAuiev88d7ynbonCU0VZoTPg/ug4OondOrLCtcGri5ltF+mausGfAYiFAQVEWqXV+1tyejoawJ884etb3n4ilpsrH9JK6AtOkEWVD3TDrNi29O1mQQ/Cn88g472zAJ+DhsIn+iehtfX5nmOtDNN/1t1bGMIBzkSYEAYwUiRJbRXvbobT7qKZQPA3dh0m8AYQS5/hd4/c4pmlxL8kgr24SnBY5 key1 name ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDTXvM7AslzjNUYrPLiNVBsF5VnqL2RmqrkzscdVdHzVxvieNwmLGeUkg8EfXPiz7j5F/Lr0J8oItTCWzyN2KmM+DhUMjvP4AbELO/VYbnVrZICRiUNYSO3EN9/uapKAuiev88d7ynbonCU0VZoTPg/ug4OondOrLCtcGri5ltF+mausGfAYiFAQVEWqXV+1tyejoawJ884etb3n4ilpsrH9JK6AtOkEWVD3TDrNi29O1mQQ/Cn88g472zAJ+DhsIn+iehtfX5nmOtDNN/1t1bGMIBzkSYEAYwUiRJbRXvbobT7qKZQPA3dh0m8AYQS5/hd4/c4pmlxL8kgr24SnBY5 keyname2 #ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDTXvM7AslzjNUYrPLiNVBsF5VnqL2RmqrkzscdVdHzVxvieNwmLGeUkg8EfXPiz7j5F/Lr0J8oItTCWzyN2KmM+DhUMjvP4AbELO/VYbnVrZICRiUNYSO3EN9/uapKAuiev88d7ynbonCU0VZoTPg/ug4OondOrLCtcGri5ltF+mausGfAYiFAQVEWqXV+1tyejoawJ884etb3n4ilpsrH9JK6AtOkEWVD3TDrNi29O1mQQ/Cn88g472zAJ+DhsIn+iehtfX5nmOtDNN/1t1bGMIBzkSYEAYwUiRJbRXvbobT7qKZQPA3dh0m8AYQS5/hd4/c4pmlxL8kgr24SnBY5 keyname3 +ssh-rsa KEY-WITH-NO-NAME diff --git a/spec/integration/application/apply_spec.rb b/spec/integration/application/apply_spec.rb index e48808120..3ce1277d3 100755 --- a/spec/integration/application/apply_spec.rb +++ b/spec/integration/application/apply_spec.rb @@ -1,7 +1,5 @@ -#! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/files' -require 'puppet/application/apply' describe "apply" do include PuppetSpec::Files @@ -17,9 +15,8 @@ describe "apply" do resource = Puppet::Resource.new(:file, file_to_create, :parameters => {:content => "my stuff"}) catalog.add_resource resource - manifest = tmpfile("manifest") + manifest = file_containing("manifest", catalog.to_pson) - File.open(manifest, "w") { |f| f.print catalog.to_pson } puppet = Puppet::Application[:apply] puppet.options[:catalog] = manifest @@ -31,12 +28,7 @@ describe "apply" do end it "applies a given file even when a directory environment is specified" do - manifest = tmpfile("manifest.pp") - File.open(manifest, "w") do |f| - f.puts <<-EOF - notice('it was applied') - EOF - end + manifest = file_containing("manifest.pp", "notice('it was applied')") special = Puppet::Node::Environment.create(:special, []) Puppet.override(:current_environment => special) do @@ -49,27 +41,41 @@ describe "apply" do expect(@logs.map(&:to_s)).to include('it was applied') end + it "applies a given file even when an ENC is configured", :if => !Puppet.features.microsoft_windows? do + manifest = file_containing("manifest.pp", "notice('specific manifest applied')") + site_manifest = file_containing("site_manifest.pp", "notice('the site manifest was applied instead')") + enc = file_containing("enc_script", "#!/bin/sh\necho 'classes: []'") + File.chmod(0755, enc) + + special = Puppet::Node::Environment.create(:special, []) + Puppet.override(:current_environment => special) do + Puppet[:environment] = 'special' + Puppet[:node_terminus] = 'exec' + Puppet[:external_nodes] = enc + Puppet[:manifest] = site_manifest + puppet = Puppet::Application[:apply] + puppet.stubs(:command_line).returns(stub('command_line', :args => [manifest])) + expect { puppet.run_command }.to exit_with(0) + end + + expect(@logs.map(&:to_s)).to include('specific manifest applied') + end + context "with a module" do let(:modulepath) { tmpdir('modulepath') } let(:execute) { 'include amod' } let(:args) { ['-e', execute, '--modulepath', modulepath] } before(:each) do - Puppet::FileSystem.mkpath("#{modulepath}/amod/manifests") - File.open("#{modulepath}/amod/manifests/init.pp", "w") do |f| - f.puts <<-EOF - class amod{ - notice('amod class included') + dir_contained_in(modulepath, { + "amod" => { + "manifests" => { + "init.pp" => "class amod{ notice('amod class included') }" + } } - EOF - end - environmentdir = Dir.mktmpdir('environments') - Puppet[:environmentpath] = environmentdir - create_default_directory_environment - end + }) - def create_default_directory_environment - Puppet::FileSystem.mkpath("#{Puppet[:environmentpath]}/#{Puppet[:environment]}") + Puppet[:environmentpath] = dir_containing("environments", { Puppet[:environment] => {} }) end def init_cli_args_and_apply_app(args, execute) diff --git a/spec/integration/parser/future_compiler_spec.rb b/spec/integration/parser/future_compiler_spec.rb index d0fcfcdec..9b612e400 100644 --- a/spec/integration/parser/future_compiler_spec.rb +++ b/spec/integration/parser/future_compiler_spec.rb @@ -440,6 +440,16 @@ describe "Puppet::Parser::Compiler" do expect(catalog).to have_resource("Foo[test]").with_parameter(:x, 'say friend') end + it 'accepts undef as the default for an Optional argument' do + catalog = compile_to_catalog(<<-MANIFEST) + define foo(Optional[String] $x = undef) { + notify { "expected": message => $x == undef } + } + foo { 'test': } + MANIFEST + expect(catalog).to have_resource("Notify[expected]").with_parameter(:message, true) + end + it 'accepts anything when parameters are untyped' do expect do catalog = compile_to_catalog(<<-MANIFEST) @@ -458,6 +468,15 @@ describe "Puppet::Parser::Compiler" do end.to raise_error(/type Integer, got String/) end + it 'denies undef for a non-optional type' do + expect do + catalog = compile_to_catalog(<<-MANIFEST) + define foo(Integer $x) { } + foo { 'test': x => undef } + MANIFEST + end.to raise_error(/type Integer, got Undef/) + end + it 'denies non type compliant default argument' do expect do catalog = compile_to_catalog(<<-MANIFEST) @@ -467,6 +486,15 @@ describe "Puppet::Parser::Compiler" do end.to raise_error(/type Integer, got String/) end + it 'denies undef as the default for a non-optional type' do + expect do + catalog = compile_to_catalog(<<-MANIFEST) + define foo(Integer $x = undef) { } + foo { 'test': } + MANIFEST + end.to raise_error(/type Integer, got Undef/) + end + it 'accepts a Resource as a Type' do catalog = compile_to_catalog(<<-MANIFEST) define foo(Type[Bar] $x) { @@ -489,6 +517,16 @@ describe "Puppet::Parser::Compiler" do expect(catalog).to have_resource("Class[Foo]").with_parameter(:x, 'say friend') end + it 'accepts undef as the default for an Optional argument' do + catalog = compile_to_catalog(<<-MANIFEST) + class foo(Optional[String] $x = undef) { + notify { "expected": message => $x == undef } + } + class { 'foo': } + MANIFEST + expect(catalog).to have_resource("Notify[expected]").with_parameter(:message, true) + end + it 'accepts anything when parameters are untyped' do expect do catalog = compile_to_catalog(<<-MANIFEST) @@ -507,6 +545,15 @@ describe "Puppet::Parser::Compiler" do end.to raise_error(/type Integer, got String/) end + it 'denies undef for a non-optional type' do + expect do + catalog = compile_to_catalog(<<-MANIFEST) + class foo(Integer $x) { } + class { 'foo': x => undef } + MANIFEST + end.to raise_error(/type Integer, got Undef/) + end + it 'denies non type compliant default argument' do expect do catalog = compile_to_catalog(<<-MANIFEST) @@ -516,6 +563,15 @@ describe "Puppet::Parser::Compiler" do end.to raise_error(/type Integer, got String/) end + it 'denies undef as the default for a non-optional type' do + expect do + catalog = compile_to_catalog(<<-MANIFEST) + class foo(Integer $x = undef) { } + class { 'foo': } + MANIFEST + end.to raise_error(/type Integer, got Undef/) + end + it 'accepts a Resource as a Type' do catalog = compile_to_catalog(<<-MANIFEST) class foo(Type[Bar] $x) { diff --git a/spec/integration/type/user_spec.rb b/spec/integration/type/user_spec.rb index 4724fe9d5..c542e51a9 100644 --- a/spec/integration/type/user_spec.rb +++ b/spec/integration/type/user_spec.rb @@ -8,7 +8,13 @@ describe Puppet::Type.type(:user), '(integration)', :unless => Puppet.features.m include PuppetSpec::Compiler context "when set to purge ssh keys from a file" do - let(:tempfile) { file_containing('user_spec', "# comment\nssh-rsa KEY-DATA key-name\nssh-rsa KEY-DATA key name\n") } + let(:tempfile) do + file_containing('user_spec', <<-EOF) + # comment + ssh-rsa KEY-DATA key-name + ssh-rsa KEY-DATA key name + EOF + end # must use an existing user, or the generated key resource # will fail on account of an invalid user for the key # - root should be a safe default @@ -32,5 +38,20 @@ describe Puppet::Type.type(:user), '(integration)', :unless => Puppet.features.m File.read(tempfile).should_not =~ /key-name/ end end + + context "with multiple unnamed keys" do + let(:tempfile) do + file_containing('user_spec', <<-EOF) + # comment + ssh-rsa KEY-DATA1 + ssh-rsa KEY-DATA2 + EOF + end + + it "should purge authorized ssh keys" do + apply_compiled_manifest(manifest) + File.read(tempfile).should_not =~ /KEY-DATA/ + end + end end end diff --git a/spec/lib/puppet_spec/files.rb b/spec/lib/puppet_spec/files.rb index 312c4fc95..2d0d95275 100755 --- a/spec/lib/puppet_spec/files.rb +++ b/spec/lib/puppet_spec/files.rb @@ -57,6 +57,7 @@ module PuppetSpec::Files dir_contained_in(tmpdir(name), contents_hash) end + def dir_contained_in(dir, contents_hash) PuppetSpec::Files.dir_contained_in(dir, contents_hash) end def self.dir_contained_in(dir, contents_hash) contents_hash.each do |k,v| if v.is_a?(Hash) diff --git a/spec/unit/environments_spec.rb b/spec/unit/environments_spec.rb index 80a0bb2d5..fe0814814 100644 --- a/spec/unit/environments_spec.rb +++ b/spec/unit/environments_spec.rb @@ -97,6 +97,17 @@ describe Puppet::Environments do end end + it "raises error when environment not found" do + directory_tree = FS::MemoryFile.a_directory(File.expand_path("envdir"), []) + + loader_from(:filesystem => [directory_tree], + :directory => directory_tree) do |loader| + expect do + loader.get!("does_not_exist") + end.to raise_error(Puppet::Environments::EnvironmentNotFound) + end + end + it "returns nil if an environment can't be found" do directory_tree = FS::MemoryFile.a_directory("envdir", []) @@ -106,6 +117,17 @@ describe Puppet::Environments do end end + it "raises error if an environment can't be found" do + directory_tree = FS::MemoryFile.a_directory("envdir", []) + + loader_from(:filesystem => [directory_tree], + :directory => directory_tree) do |loader| + expect do + loader.get!("env_not_in_this_list") + end.to raise_error(Puppet::Environments::EnvironmentNotFound) + end + end + context "with an environment.conf" do let(:envdir) do FS::MemoryFile.a_directory(File.expand_path("envdir"), [ @@ -295,6 +317,49 @@ config_version=$vardir/random/scripts with_config_version(File.expand_path('/some/script')) end end + + it "should update environment settings if environment.conf has changed and timeout has expired" do + base_dir = File.expand_path("envdir") + original_envdir = FS::MemoryFile.a_directory(base_dir, [ + FS::MemoryFile.a_directory("env3", [ + FS::MemoryFile.a_regular_file_containing("environment.conf", <<-EOF) + manifest=/manifest_orig + modulepath=/modules_orig + environment_timeout=0 + EOF + ]), + ]) + + FS.overlay(original_envdir) do + dir_loader = Puppet::Environments::Directories.new(original_envdir, []) + loader = Puppet::Environments::Cached.new(dir_loader) + Puppet.override(:environments => loader) do + original_env = loader.get("env3") # force the environment.conf to be read + + changed_envdir = FS::MemoryFile.a_directory(base_dir, [ + FS::MemoryFile.a_directory("env3", [ + FS::MemoryFile.a_regular_file_containing("environment.conf", <<-EOF) + manifest=/manifest_changed + modulepath=/modules_changed + environment_timeout=0 + EOF + ]), + ]) + + FS.overlay(changed_envdir) do + changed_env = loader.get("env3") + + expect(original_env).to environment(:env3). + with_manifest(File.expand_path("/manifest_orig")). + with_full_modulepath([File.expand_path("/modules_orig")]) + + expect(changed_env).to environment(:env3). + with_manifest(File.expand_path("/manifest_changed")). + with_full_modulepath([File.expand_path("/modules_changed")]) + end + end + end + end end end @@ -315,6 +380,12 @@ config_version=$vardir/random/scripts expect(loader.get(:doesnotexist)).to be_nil end + it "raises error if environment is not found" do + expect do + loader.get!(:doesnotexist) + end.to raise_error(Puppet::Environments::EnvironmentNotFound) + end + it "gets a basic conf" do conf = loader.get_conf(:static1) expect(conf.modulepath).to eq('') @@ -336,11 +407,34 @@ config_version=$vardir/random/scripts end end + + describe "cached loaders" do + let(:cached1) { Puppet::Node::Environment.create(:cached1, []) } + let(:cached2) { Puppet::Node::Environment.create(:cached2, []) } + let(:static_loader) { Puppet::Environments::Static.new(cached1, cached2) } + let(:loader) { Puppet::Environments::Cached.new(static_loader) } + + it "gets an environment" do + expect(loader.get(:cached2)).to eq(cached2) + end + + it "returns nil if env not found" do + expect(loader.get(:doesnotexist)).to be_nil + end + + it "raises error if environment is not found" do + expect do + loader.get!(:doesnotexist) + end.to raise_error(Puppet::Environments::EnvironmentNotFound) + end + end + RSpec::Matchers.define :environment do |name| match do |env| env.name == name && (!@manifest || @manifest == env.manifest) && (!@modulepath || @modulepath == env.modulepath) && + (!@full_modulepath || @full_modulepath == env.full_modulepath) && (!@config_version || @config_version == env.config_version) end @@ -352,6 +446,10 @@ config_version=$vardir/random/scripts @modulepath = modulepath end + chain :with_full_modulepath do |full_modulepath| + @full_modulepath = full_modulepath + end + chain :with_config_version do |config_version| @config_version = config_version end @@ -360,6 +458,7 @@ config_version=$vardir/random/scripts "environment #{expected}" + (@manifest ? " with manifest #{@manifest}" : "") + (@modulepath ? " with modulepath [#{@modulepath.join(', ')}]" : "") + + (@full_modulepath ? " with full_modulepath [#{@full_modulepath.join(', ')}]" : "") + (@config_version ? " with config_version #{@config_version}" : "") end diff --git a/spec/unit/network/http/webrick_spec.rb b/spec/unit/network/http/webrick_spec.rb index edeb439a9..43662e0b4 100755 --- a/spec/unit/network/http/webrick_spec.rb +++ b/spec/unit/network/http/webrick_spec.rb @@ -22,6 +22,10 @@ describe Puppet::Network::HTTP::WEBrick do s end + let(:mock_ssl_context) do + stub('ssl_context', :ciphers= => nil) + end + let(:mock_webrick) do stub('webrick', :[] => {}, @@ -29,7 +33,8 @@ describe Puppet::Network::HTTP::WEBrick do :status => :Running, :mount => nil, :start => nil, - :shutdown => nil) + :shutdown => nil, + :ssl_context => mock_ssl_context) end before :each do @@ -251,7 +256,15 @@ describe Puppet::Network::HTTP::WEBrick do end it "should reject SSLv2" do - server.setup_ssl[:SSLOptions].should == OpenSSL::SSL::OP_NO_SSLv2 + options = server.setup_ssl[:SSLOptions] + + expect(options & OpenSSL::SSL::OP_NO_SSLv2).to eq(OpenSSL::SSL::OP_NO_SSLv2) + end + + it "should reject SSLv3" do + options = server.setup_ssl[:SSLOptions] + + expect(options & OpenSSL::SSL::OP_NO_SSLv3).to eq(OpenSSL::SSL::OP_NO_SSLv3) end it "should configure the verification method as 'OpenSSL::SSL::VERIFY_PEER'" do @@ -267,5 +280,11 @@ describe Puppet::Network::HTTP::WEBrick do it "should set the certificate name to 'nil'" do server.setup_ssl[:SSLCertName].should be_nil end + + it "specifies the allowable ciphers" do + mock_ssl_context.expects(:ciphers=).with(server.class::CIPHERS) + + server.create_server('localhost', '8888') + end end end diff --git a/spec/unit/parser/compiler_spec.rb b/spec/unit/parser/compiler_spec.rb index 0ca01f521..74cb326d9 100755 --- a/spec/unit/parser/compiler_spec.rb +++ b/spec/unit/parser/compiler_spec.rb @@ -1,6 +1,6 @@ -#! /usr/bin/env ruby require 'spec_helper' require 'puppet_spec/compiler' +require 'matchers/resource' class CompilerTestResource attr_accessor :builtin, :virtual, :evaluated, :type, :title @@ -53,6 +53,7 @@ end describe Puppet::Parser::Compiler do include PuppetSpec::Files + include Matchers::Resource def resource(type, title) Puppet::Parser::Resource.new(type, title, :scope => @scope) @@ -864,6 +865,23 @@ describe Puppet::Parser::Compiler do it "should fail if the class doesn't exist" do expect { compile_to_catalog('', node) }.to raise_error(Puppet::Error, /Could not find class something/) end + + it 'evaluates classes declared with parameters before unparameterized classes' do + node = Puppet::Node.new('someone', :classes => { 'app::web' => {}, 'app' => { 'port' => 8080 } }) + manifest = <<-MANIFEST + class app($port = 80) { } + + class app::web($port = $app::port) inherits app { + notify { expected: message => "$port" } + } + MANIFEST + + catalog = compile_to_catalog(manifest, node) + + expect(catalog).to have_resource("Class[App]").with_parameter(:port, 8080) + expect(catalog).to have_resource("Class[App::Web]") + expect(catalog).to have_resource("Notify[expected]").with_parameter(:message, "8080") + end end end end diff --git a/spec/unit/parser/functions/lookup_spec.rb b/spec/unit/parser/functions/lookup_spec.rb index b6d909b6f..54ea55423 100644 --- a/spec/unit/parser/functions/lookup_spec.rb +++ b/spec/unit/parser/functions/lookup_spec.rb @@ -8,6 +8,7 @@ describe "lookup function" do before(:each) do Puppet[:binder] = true + Puppet[:parser] = 'future' end it "must be called with at least a name to lookup" do @@ -46,9 +47,9 @@ describe "lookup function" do expect(scope.function_lookup(['a_value'])).to eq('from_hiera') end - it "returns :undef when the requested value is not bound and undef is accepted" do + it "returns nil when the requested value is not bound and undef is accepted" do scope = scope_with_injections_from(bound(bind_single("a_value", "something"))) - expect(scope.function_lookup(['not_bound_value',{'accept_undef' => true}])).to eq(:undef) + expect(scope.function_lookup(['not_bound_value',{'accept_undef' => true}])).to eq(nil) end it "fails if the requested value is not bound and undef is not allowed" do @@ -84,35 +85,35 @@ describe "lookup function" do it "yields to a given lambda and returns the result" do scope = scope_with_injections_from(bound(bind_single("a_value", "something"))) - expect(scope.function_lookup(['a_value', ast_lambda('|$x|{something_else}')])).to eq('something_else') + expect(scope.function_lookup(['a_value', ast_lambda(scope, '|$x|{something_else}')])).to eq('something_else') end it "fails if given lambda produces undef" do scope = scope_with_injections_from(bound(bind_single("a_value", "something"))) expect do - scope.function_lookup(['a_value', ast_lambda('|$x|{undef}')]) + scope.function_lookup(['a_value', ast_lambda(scope, '|$x|{undef}')]) end.to raise_error(/did not find a value for the name 'a_value'/) end it "yields name and result to a given lambda" do scope = scope_with_injections_from(bound(bind_single("a_value", "something"))) - expect(scope.function_lookup(['a_value', ast_lambda('|$name, $result|{[$name, $result]}')])).to eq(['a_value', 'something']) + expect(scope.function_lookup(['a_value', ast_lambda(scope, '|$name, $result|{[$name, $result]}')])).to eq(['a_value', 'something']) end it "yields name and result and default to a given lambda" do scope = scope_with_injections_from(bound(bind_single("a_value", "something"))) expect(scope.function_lookup(['a_value', {'default' => 'cigar'}, - ast_lambda('|$name, $result, $d|{[$name, $result, $d]}')])).to eq(['a_value', 'something', 'cigar']) + ast_lambda(scope, '|$name, $result, $d|{[$name, $result, $d]}')])).to eq(['a_value', 'something', 'cigar']) end it "yields to a given lambda and returns the result when giving name and type" do scope = scope_with_injections_from(bound(bind_single("a_value", "something"))) - expect(scope.function_lookup(['a_value', 'String', ast_lambda('|$x|{something_else}')])).to eq('something_else') + expect(scope.function_lookup(['a_value', 'String', ast_lambda(scope, '|$x|{something_else}')])).to eq('something_else') end it "yields :undef when value is not found and using a lambda" do scope = scope_with_injections_from(bound(bind_single("a_value", "something"))) - expect(scope.function_lookup(['not_bound_value', ast_lambda('|$x|{ if $x == undef {good} else {bad}}')])).to eq('good') + expect(scope.function_lookup(['not_bound_value', ast_lambda(scope, '|$x|{ if $x == undef {good} else {bad}}')])).to eq('good') end def scope_with_injections_from(binder) @@ -137,10 +138,10 @@ describe "lookup function" do Puppet::Pops::Binder::Binder.new(layered_bindings) end - def ast_lambda(puppet_source) + def ast_lambda(scope, puppet_source) puppet_source = "fake_func() " + puppet_source - model = Puppet::Pops::Parser::EvaluatingParser.new().parse_string(puppet_source, __FILE__).current - model = model.body.lambda - Puppet::Pops::Model::AstTransformer.new(@file_source, nil).transform(model) + evaluator = Puppet::Pops::Parser::EvaluatingParser.new() + model = evaluator.parse_string(puppet_source, __FILE__).current + evaluator.closure(model.body.lambda, scope) end end diff --git a/spec/unit/parser/resource/param_spec.rb b/spec/unit/parser/resource/param_spec.rb index 7989d060d..dcb8a3616 100755 --- a/spec/unit/parser/resource/param_spec.rb +++ b/spec/unit/parser/resource/param_spec.rb @@ -1,19 +1,13 @@ -#! /usr/bin/env ruby require 'spec_helper' describe Puppet::Parser::Resource::Param do - it "can be instantiated" do - Puppet::Parser::Resource::Param.new(:name => 'myparam', :value => 'foo') - end - - it "stores the source file" do - param = Puppet::Parser::Resource::Param.new(:name => 'myparam', :value => 'foo', :file => 'foo.pp') - param.file.should == 'foo.pp' - end + it "has readers for all of the attributes" do + param = Puppet::Parser::Resource::Param.new(:name => 'myparam', :value => 'foo', :file => 'foo.pp', :line => 42) - it "stores the line number" do - param = Puppet::Parser::Resource::Param.new(:name => 'myparam', :value => 'foo', :line => 42) - param.line.should == 42 + expect(param.name).to eq(:myparam) + expect(param.value).to eq('foo') + expect(param.file).to eq('foo.pp') + expect(param.line).to eq(42) end context "parameter validation" do @@ -23,21 +17,15 @@ describe Puppet::Parser::Resource::Param do }.to raise_error(Puppet::Error, /name is a required option/) end - it "throws an error when instantiated without a value" do - expect { - Puppet::Parser::Resource::Param.new(:name => 'myparam') - }.to raise_error(Puppet::Error, /value is a required option/) - end + it "does not require a value" do + param = Puppet::Parser::Resource::Param.new(:name => 'myparam') - it "throws an error when instantiated with a nil value" do - expect { - Puppet::Parser::Resource::Param.new(:name => 'myparam', :value => nil) - }.to raise_error(Puppet::Error, /value is a required option/) + expect(param.value).to be_nil end it "includes file/line context in errors" do expect { - Puppet::Parser::Resource::Param.new(:name => 'myparam', :value => nil, :file => 'foo.pp', :line => 42) + Puppet::Parser::Resource::Param.new(:file => 'foo.pp', :line => 42) }.to raise_error(Puppet::Error, /foo.pp:42/) end end diff --git a/spec/unit/parser/resource_spec.rb b/spec/unit/parser/resource_spec.rb index d0ec5f49c..97f19e21a 100755 --- a/spec/unit/parser/resource_spec.rb +++ b/spec/unit/parser/resource_spec.rb @@ -561,10 +561,6 @@ describe Puppet::Parser::Resource do @resource["foo"].should == "bar" end - it "should fail when provided a parameter name but no value" do - expect { @resource.set_parameter("myparam") }.to raise_error(ArgumentError) - end - it "should allow parameters to be set to 'false'" do @resource.set_parameter("myparam", false) @resource["myparam"].should be_false diff --git a/spec/unit/pops/evaluator/evaluating_parser_spec.rb b/spec/unit/pops/evaluator/evaluating_parser_spec.rb index 5e80e4076..bc5b8bd66 100644 --- a/spec/unit/pops/evaluator/evaluating_parser_spec.rb +++ b/spec/unit/pops/evaluator/evaluating_parser_spec.rb @@ -680,12 +680,12 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do "'abc'[x]" => "The value 'x' cannot be converted to Numeric", "'abc'[1.0]" => "A String[] cannot use Float where Integer is expected", "'abc'[1,2,3]" => "String supports [] with one or two arguments. Got 3", - "Resource[0]" => 'First argument to Resource[] must be a resource type or a String. Got Fixnum', - "Resource[a, 0]" => 'Error creating type specialization of a Resource-Type, Cannot use Fixnum where String is expected', - "File[0]" => 'Error creating type specialization of a File-Type, Cannot use Fixnum where String is expected', + "Resource[0]" => 'First argument to Resource[] must be a resource type or a String. Got Integer', + "Resource[a, 0]" => 'Error creating type specialization of a Resource-Type, Cannot use Integer where a resource title String is expected', + "File[0]" => 'Error creating type specialization of a File-Type, Cannot use Integer where a resource title String is expected', "String[a]" => "A Type's size constraint arguments must be a single Integer type, or 1-2 integers (or default). Got a String", - "Pattern[0]" => 'Error creating type specialization of a Pattern-Type, Cannot use Fixnum where String or Regexp or Pattern-Type or Regexp-Type is expected', - "Regexp[0]" => 'Error creating type specialization of a Regexp-Type, Cannot use Fixnum where String or Regexp is expected', + "Pattern[0]" => 'Error creating type specialization of a Pattern-Type, Cannot use Integer where String or Regexp or Pattern-Type or Regexp-Type is expected', + "Regexp[0]" => 'Error creating type specialization of a Regexp-Type, Cannot use Integer where String or Regexp is expected', "Regexp[a,b]" => 'A Regexp-Type[] accepts 1 argument. Got 2', "true[0]" => "Operator '[]' is not applicable to a Boolean", "1[0]" => "Operator '[]' is not applicable to an Integer", @@ -963,6 +963,31 @@ describe 'Puppet::Pops::Evaluator::EvaluatorImpl' do expect{parser.evaluate_string(scope, "assert_no_undef({undef => 1})")}.to_not raise_error() expect{parser.evaluate_string(scope, "assert_no_undef({1 => undef})")}.to_not raise_error() end + + context 'using the 3x function api' do + it 'can call a 3x function' do + Puppet::Parser::Functions.newfunction("bazinga", :type => :rvalue) { |args| args[0] } + parser.evaluate_string(scope, "bazinga(42)", __FILE__).should == 42 + end + + it 'maps :undef to empty string' do + Puppet::Parser::Functions.newfunction("bazinga", :type => :rvalue) { |args| args[0] } + parser.evaluate_string(scope, "$a = {} bazinga($a[nope])", __FILE__).should == '' + parser.evaluate_string(scope, "bazinga(undef)", __FILE__).should == '' + end + + it 'does not map :undef to empty string in arrays' do + Puppet::Parser::Functions.newfunction("bazinga", :type => :rvalue) { |args| args[0][0] } + parser.evaluate_string(scope, "$a = {} $b = [$a[nope]] bazinga($b)", __FILE__).should == :undef + parser.evaluate_string(scope, "bazinga([undef])", __FILE__).should == :undef + end + + it 'does not map :undef to empty string in hashes' do + Puppet::Parser::Functions.newfunction("bazinga", :type => :rvalue) { |args| args[0]['a'] } + parser.evaluate_string(scope, "$a = {} $b = {a => $a[nope]} bazinga($b)", __FILE__).should == :undef + parser.evaluate_string(scope, "bazinga({a => undef})", __FILE__).should == :undef + end + end end context "When evaluator performs string interpolation" do diff --git a/spec/unit/pops/parser/parse_calls_spec.rb b/spec/unit/pops/parser/parse_calls_spec.rb index ee80544f5..6f75eabc8 100644 --- a/spec/unit/pops/parser/parse_calls_spec.rb +++ b/spec/unit/pops/parser/parse_calls_spec.rb @@ -59,11 +59,6 @@ describe "egrammar parsing function calls" do dump(parse("$a = foo()")).should == "(= $a (call foo))" end - # # For regular grammar where a bare word can not be a "statement" - # it "$a = foo bar # illegal, must have parentheses" do - # expect { dump(parse("$a = foo bar"))}.to raise_error(Puppet::ParseError) - # end - # For egrammar where a bare word can be a "statement" it "$a = foo bar # illegal, must have parentheses" do dump(parse("$a = foo bar")).should == "(block\n (= $a foo)\n bar\n)" @@ -101,4 +96,24 @@ describe "egrammar parsing function calls" do ].join("\n") end end + + context "When parsing an illegal argument list" do + it "raises an error if argument list is not for a call" do + expect do + parse("$a = 10, 3") + end.to raise_error(/illegal comma/) + end + + it "raises an error if argument list is for a potential call not allowed without parentheses" do + expect do + parse("foo 10, 3") + end.to raise_error(/attempt to pass argument list to the function 'foo' which cannot be called without parentheses/) + end + + it "does no raise an error for an argument list to an allowed call" do + expect do + parse("notice 10, 3") + end.to_not raise_error() + end + end end diff --git a/spec/unit/pops/types/type_calculator_spec.rb b/spec/unit/pops/types/type_calculator_spec.rb index 0bd475263..bb0c890a8 100644 --- a/spec/unit/pops/types/type_calculator_spec.rb +++ b/spec/unit/pops/types/type_calculator_spec.rb @@ -10,6 +10,9 @@ describe 'The type calculator' do t.to = to t end + def constrained_t(t, from, to) + Puppet::Pops::Types::TypeFactory.constrain_size(t, from, to) + end def pattern_t(*patterns) Puppet::Pops::Types::TypeFactory.pattern(*patterns) @@ -827,6 +830,21 @@ describe 'The type calculator' do calculator.assignable?(pattern, variant_t(string_t('a'), string_t('b'))).should == true end + it 'pattern representing all patterns should accept any pattern' do + calculator.assignable?(pattern_t(), pattern_t('a')).should == true + calculator.assignable?(pattern_t(), pattern_t()).should == true + end + + it 'pattern representing all patterns should accept any enum' do + calculator.assignable?(pattern_t(), enum_t('a')).should == true + calculator.assignable?(pattern_t(), enum_t()).should == true + end + + it 'pattern representing all patterns should accept any string' do + calculator.assignable?(pattern_t(), string_t('a')).should == true + calculator.assignable?(pattern_t(), string_t()).should == true + end + end context 'when dealing with enums' do @@ -842,12 +860,51 @@ describe 'The type calculator' do calculator.assignable?(enum_t('a', 'b'), enum_t('c')).should == false end + it 'non parameterized enum accepts any other enum but not the reverse' do + calculator.assignable?(enum_t(), enum_t('a')).should == true + calculator.assignable?(enum_t('a'), enum_t()).should == false + end + it 'enum should accept a variant where all variants are acceptable' do enum = enum_t('a', 'b') calculator.assignable?(enum, variant_t(string_t('a'), string_t('b'))).should == true end end + context 'when dealing with string and enum combinations' do + it 'should accept assigning any enum to unrestricted string' do + calculator.assignable?(string_t(), enum_t('blue')).should == true + calculator.assignable?(string_t(), enum_t('blue', 'red')).should == true + end + + it 'should not accept assigning longer enum value to size restricted string' do + calculator.assignable?(constrained_t(string_t(),2,2), enum_t('a','blue')).should == false + end + + it 'should accept assigning any string to empty enum' do + calculator.assignable?(enum_t(), string_t()).should == true + end + + it 'should accept assigning empty enum to any string' do + calculator.assignable?(string_t(), enum_t()).should == true + end + + it 'should not accept assigning empty enum to size constrained string' do + calculator.assignable?(constrained_t(string_t(),2,2), enum_t()).should == false + end + end + + context 'when dealing with string/pattern/enum combinations' do + it 'any string is equal to any enum is equal to any pattern' do + calculator.assignable?(string_t(), enum_t()).should == true + calculator.assignable?(string_t(), pattern_t()).should == true + calculator.assignable?(enum_t(), string_t()).should == true + calculator.assignable?(enum_t(), pattern_t()).should == true + calculator.assignable?(pattern_t(), string_t()).should == true + calculator.assignable?(pattern_t(), enum_t()).should == true + end + end + context 'when dealing with tuples' do it 'matches empty tuples' do tuple1 = tuple_t() @@ -1057,6 +1114,10 @@ describe 'The type calculator' do calculator.instance?(Puppet::Pops::Types::PRuntimeType.new(:runtime => :ruby, :runtime_type_name => 'Symbol'), :undef).should == true end + it "should consider :undef to be instance of an Optional type" do + calculator.instance?(Puppet::Pops::Types::POptionalType.new(), :undef).should == true + end + it 'should not consider undef to be an instance of any other type than Any, NilType and Data' do types_to_test = all_types - [ Puppet::Pops::Types::PAnyType, diff --git a/spec/unit/provider/package/pkg_spec.rb b/spec/unit/provider/package/pkg_spec.rb index 8ddbbbfe0..9442b7f21 100755 --- a/spec/unit/provider/package/pkg_spec.rb +++ b/spec/unit/provider/package/pkg_spec.rb @@ -70,6 +70,10 @@ describe Puppet::Type.type(:package).provider(:pkg) do described_class.expects(:pkg).with(:list,'-Hn','dummy').returns File.read(my_fixture('dummy_solaris11.installed')) provider.latest.should == "1.0.6-0.175.0.0.0.2.537" end + it "should work correctly for ensure latest on solaris 11 in the presence of a certificate expiration warning" do + described_class.expects(:pkg).with(:list,'-Hn','dummy').returns File.read(my_fixture('dummy_solaris11.certificate_warning')) + provider.latest.should == "1.0.6-0.175.0.0.0.2.537" + end it "should work correctly for ensure latest on solaris 11(known UFOXI)" do Puppet::Util::Execution.expects(:execute).with(['/bin/pkg', 'update', '-n', 'dummy'], {:failonfail => false, :combine => true}).returns '' $CHILD_STATUS.stubs(:exitstatus).returns 0 diff --git a/spec/unit/provider/scheduled_task/win32_taskscheduler_spec.rb b/spec/unit/provider/scheduled_task/win32_taskscheduler_spec.rb index 3d37956c5..270ca6bae 100644 --- a/spec/unit/provider/scheduled_task/win32_taskscheduler_spec.rb +++ b/spec/unit/provider/scheduled_task/win32_taskscheduler_spec.rb @@ -142,14 +142,14 @@ describe Puppet::Type.type(:scheduled_task).provider(:win32_taskscheduler), :if 'type' => { 'days_interval' => 2 }, }) - resource.provider.trigger.should == { + resource.provider.trigger.should == [{ 'start_date' => '2011-9-12', 'start_time' => '13:20', 'schedule' => 'daily', 'every' => '2', 'enabled' => true, 'index' => 0, - } + }] end it 'should handle a single weekly trigger' do @@ -171,7 +171,7 @@ describe Puppet::Type.type(:scheduled_task).provider(:win32_taskscheduler), :if } }) - resource.provider.trigger.should == { + resource.provider.trigger.should == [{ 'start_date' => '2011-9-12', 'start_time' => '13:20', 'schedule' => 'weekly', @@ -179,7 +179,7 @@ describe Puppet::Type.type(:scheduled_task).provider(:win32_taskscheduler), :if 'on' => ['sun', 'mon', 'wed', 'fri'], 'enabled' => true, 'index' => 0, - } + }] end it 'should handle a single monthly date-based trigger' do @@ -204,7 +204,7 @@ describe Puppet::Type.type(:scheduled_task).provider(:win32_taskscheduler), :if } }) - resource.provider.trigger.should == { + resource.provider.trigger.should == [{ 'start_date' => '2011-9-12', 'start_time' => '13:20', 'schedule' => 'monthly', @@ -212,7 +212,7 @@ describe Puppet::Type.type(:scheduled_task).provider(:win32_taskscheduler), :if 'on' => [1, 3, 5, 15, 'last'], 'enabled' => true, 'index' => 0, - } + }] end it 'should handle a single monthly day-of-week-based trigger' do @@ -240,7 +240,7 @@ describe Puppet::Type.type(:scheduled_task).provider(:win32_taskscheduler), :if } }) - resource.provider.trigger.should == { + resource.provider.trigger.should == [{ 'start_date' => '2011-9-12', 'start_time' => '13:20', 'schedule' => 'monthly', @@ -249,7 +249,7 @@ describe Puppet::Type.type(:scheduled_task).provider(:win32_taskscheduler), :if 'day_of_week' => ['sun', 'mon', 'wed', 'fri'], 'enabled' => true, 'index' => 0, - } + }] end it 'should handle a single one-time trigger' do @@ -263,13 +263,13 @@ describe Puppet::Type.type(:scheduled_task).provider(:win32_taskscheduler), :if 'flags' => 0, }) - resource.provider.trigger.should == { + resource.provider.trigger.should == [{ 'start_date' => '2011-9-12', 'start_time' => '13:20', 'schedule' => 'once', 'enabled' => true, 'index' => 0, - } + }] end end @@ -536,18 +536,18 @@ describe Puppet::Type.type(:scheduled_task).provider(:win32_taskscheduler), :if 'index' => 0, } - resource.provider.trigger.should == mock_task_trigger - resource.provider.trigger.should == mock_task_trigger + resource.provider.trigger.should == [mock_task_trigger] + resource.provider.trigger.should == [mock_task_trigger] resource.provider.clear_task - resource.provider.trigger.should == { + resource.provider.trigger.should == [{ 'start_date' => '2012-11-14', 'start_time' => '15:22', 'schedule' => 'once', 'enabled' => true, 'index' => 0, - } + }] end end @@ -635,6 +635,30 @@ describe Puppet::Type.type(:scheduled_task).provider(:win32_taskscheduler), :if describe '#triggers_same?' do let(:provider) { described_class.new(:name => 'foobar', :command => 'C:\Windows\System32\notepad.exe') } + it "should not mutate triggers" do + current = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3} + current.freeze + + desired = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30'} + desired.freeze + + expect(provider).to be_triggers_same(current, desired) + end + + it "ignores 'index' in current trigger" do + current = {'index' => 0, 'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3} + desired = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3} + + expect(provider).to be_triggers_same(current, desired) + end + + it "ignores 'enabled' in current triggger" do + current = {'enabled' => true, 'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3} + desired = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3} + + expect(provider).to be_triggers_same(current, desired) + end + it "should not consider a disabled 'current' trigger to be the same" do current = {'schedule' => 'once', 'enabled' => false} desired = {'schedule' => 'once'} @@ -649,6 +673,15 @@ describe Puppet::Type.type(:scheduled_task).provider(:win32_taskscheduler), :if provider.should_not be_triggers_same(current, desired) end + describe 'start_date' do + it "considers triggers to be equal when start_date is not specified in the 'desired' trigger" do + current = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3} + desired = {'schedule' => 'daily', 'start_time' => '15:30', 'every' => 3} + + provider.should be_triggers_same(current, desired) + end + end + describe 'comparing daily triggers' do it "should consider 'desired' triggers not specifying 'every' to have the same value as the 'current' trigger" do current = {'schedule' => 'daily', 'start_date' => '2011-09-12', 'start_time' => '15:30', 'every' => 3} diff --git a/spec/unit/provider/ssh_authorized_key/parsed_spec.rb b/spec/unit/provider/ssh_authorized_key/parsed_spec.rb index 2e88c57df..78abec901 100755 --- a/spec/unit/provider/ssh_authorized_key/parsed_spec.rb +++ b/spec/unit/provider/ssh_authorized_key/parsed_spec.rb @@ -136,6 +136,21 @@ describe provider_class, :unless => Puppet.features.microsoft_windows? do end end + describe "prefetch_hook" do + let(:path) { '/path/to/keyfile' } + let(:input) do + { :type => 'rsa', + :key => 'KEYDATA', + :name => '', + :record_type => :parsed, + :target => path, + } + end + it "adds an indexed name to unnamed resources" do + @provider_class.prefetch_hook([input])[0][:name].should =~ /^#{path}:unnamed-\d+/ + end + end + end describe provider_class, :unless => Puppet.features.microsoft_windows? do diff --git a/spec/unit/type/user_spec.rb b/spec/unit/type/user_spec.rb index f5a351752..974054309 100755 --- a/spec/unit/type/user_spec.rb +++ b/spec/unit/type/user_spec.rb @@ -501,6 +501,11 @@ describe Puppet::Type.type(:user) do names = resources.collect { |res| res.name } names.should_not include("keyname3") end + it "should generate names for unnamed keys" do + names = resources.collect { |res| res.name } + fixture_path = File.join(my_fixture_dir, 'authorized_keys') + names.should include("#{fixture_path}:unnamed-1") + end it "should each have a value for the user property" do resources.map { |res| res[:user] diff --git a/spec/unit/util/autoload_spec.rb b/spec/unit/util/autoload_spec.rb index 3028c9e82..d24006360 100755 --- a/spec/unit/util/autoload_spec.rb +++ b/spec/unit/util/autoload_spec.rb @@ -5,10 +5,10 @@ require 'puppet/util/autoload' describe Puppet::Util::Autoload do include PuppetSpec::Files + before do @autoload = Puppet::Util::Autoload.new("foo", "tmp") - @autoload.stubs(:eachdir).yields make_absolute("/my/dir") @loaded = {} @autoload.class.stubs(:loaded).returns(@loaded) end @@ -17,25 +17,44 @@ describe Puppet::Util::Autoload do before :each do ## modulepath/libdir can't be used until after app settings are initialized, so we need to simulate that: Puppet.settings.expects(:app_defaults_initialized?).returns(true).at_least_once - - @dira = File.expand_path('/a') - @dirb = File.expand_path('/b') - @dirc = File.expand_path('/c') end it "should collect all of the lib directories that exist in the current environment's module path" do - environment = Puppet::Node::Environment.create(:foo, [@dira, @dirb, @dirc]) - Dir.expects(:entries).with(@dira).returns %w{. .. one two} - Dir.expects(:entries).with(@dirb).returns %w{. .. one two} + dira = dir_containing('dir_a', { + "one" => {}, + "two" => { "lib" => {} } + }) - Puppet::FileSystem.expects(:directory?).with(@dira).returns true - Puppet::FileSystem.expects(:directory?).with(@dirb).returns true - Puppet::FileSystem.expects(:directory?).with(@dirc).returns false + dirb = dir_containing('dir_a', { + "one" => {}, + "two" => { "lib" => {} } + }) - FileTest.expects(:directory?).with(regexp_matches(%r{two/lib})).times(2).returns true - FileTest.expects(:directory?).with(regexp_matches(%r{one/lib})).times(2).returns false + environment = Puppet::Node::Environment.create(:foo, [dira, dirb]) + + @autoload.class.module_directories(environment).should == ["#{dira}/two/lib", "#{dirb}/two/lib"] + end + + it "ignores missing module directories" do + environment = Puppet::Node::Environment.create(:foo, [File.expand_path('does/not/exist')]) + + @autoload.class.module_directories(environment).should be_empty + end - @autoload.class.module_directories(environment).should == ["#{@dira}/two/lib", "#{@dirb}/two/lib"] + it "ignores the configured environment when it doesn't exist" do + Puppet[:environment] = 'nonexistent' + + Puppet.override({ :environments => Puppet::Environments::Static.new() }) do + @autoload.class.module_directories(nil).should be_empty + end + end + + it "uses the configured environment when no environment is given" do + Puppet[:environment] = 'nonexistent' + + Puppet.override({ :environments => Puppet::Environments::Static.new() }) do + @autoload.class.module_directories(nil).should be_empty + end end it "should include the module directories, the Puppet libdir, and all of the Ruby load directories" do diff --git a/spec/unit/util/monkey_patches_spec.rb b/spec/unit/util/monkey_patches_spec.rb index 13e10454c..7c8abb137 100755 --- a/spec/unit/util/monkey_patches_spec.rb +++ b/spec/unit/util/monkey_patches_spec.rb @@ -264,14 +264,26 @@ describe OpenSSL::SSL::SSLContext do it 'disables SSLv2 via the SSLContext#options bitmask' do (subject.options & OpenSSL::SSL::OP_NO_SSLv2).should == OpenSSL::SSL::OP_NO_SSLv2 end + + it 'disables SSLv3 via the SSLContext#options bitmask' do + (subject.options & OpenSSL::SSL::OP_NO_SSLv3).should == OpenSSL::SSL::OP_NO_SSLv3 + end + it 'explicitly disable SSLv2 ciphers using the ! prefix so they cannot be re-added' do cipher_str = OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:ciphers] cipher_str.split(':').should include('!SSLv2') end + + it 'does not exclude SSLv3 ciphers shared with TLSv1' do + cipher_str = OpenSSL::SSL::SSLContext::DEFAULT_PARAMS[:ciphers] + cipher_str.split(':').should_not include('!SSLv3') + end + it 'sets parameters on initialization' do described_class.any_instance.expects(:set_params) subject end + it 'has no ciphers with version SSLv2 enabled' do ciphers = subject.ciphers.select do |name, version, bits, alg_bits| /SSLv2/.match(version) diff --git a/tasks/memwalk.rake b/tasks/memwalk.rake new file mode 100644 index 000000000..49077d6c8 --- /dev/null +++ b/tasks/memwalk.rake @@ -0,0 +1,195 @@ +# Walks the memory dumped into heap.json, and produces a graph of the memory dumped in diff.json +# If a single argument (a hex address to one object) is given, the graph is limited to this object and what references it +# The heap dumps should be in the format produced by Ruby ObjectSpace in Ruby version 2.1.0 or later. +# +# The command produces a .dot file that can be rendered with graphwiz dot into SVG. If a memwalk is performed for all +# objects in the diff.json, the output file name is memwalk.dot. If it is produced for a single address, the name of the +# output file is memwalk-<address>.dot +# +# The dot file can be rendered with something like: dot -Tsvg -omemwalk.svg memwalk.dot +# +desc "Process a diff.json of object ids, and a heap.json of a Ruby 2.1.0 ObjectSpace dump and produce a graph" +task :memwalk, [:id] do |t, args| + puts "Memwalk" + puts "Computing for #{args[:id] ? args[:id] : 'all'}" + @single_id = args[:id] ? args[:id].to_i(16) : nil + + require 'json' + #require 'debug' + + TYPE = "type".freeze + ROOT = "root".freeze + ROOT_UC = "ROOT".freeze + ADDR = "address".freeze + NODE = "NODE".freeze + STRING = "STRING".freeze + DATA = "DATA".freeze + HASH = "HASH".freeze + ARRAY = "ARRAY".freeze + OBJECT = "OBJECT".freeze + CLASS = "CLASS".freeze + + allocations = {} + # An array of integer addresses of the objects to trace bindings for + diff_index = {} + puts "Reading data" + begin + puts "Reading diff" + lines = 0; + File.readlines("diff.json").each do | line | + lines += 1 + diff = JSON.parse(line) + case diff[ TYPE ] + when STRING, DATA, HASH, ARRAY + # skip the strings + else + diff_index[ diff[ ADDR ].to_i(16) ] = diff + end + end + puts "Read #{lines} number of diffs" + rescue => e + raise "ERROR READING DIFF at line #{lines} #{e.message[0, 200]}" + end + + begin + puts "Reading heap" + lines = 0 + allocation = nil + File.readlines("heap.json").each do | line | + lines += 1 + allocation = JSON.parse(line) + case allocation[ TYPE ] + when ROOT_UC + # Graph for single id must include roots, as it may be a root that holds on to the reference + # a global variable, thread, etc. + # + if @single_id + allocations[ allocation[ ROOT ] ] = allocation + end + when NODE + # skip the NODE objects - they represent the loaded ruby code + when STRING + # skip all strings - they are everywhere + else + allocations[ allocation[ ADDR ].to_i(16) ] = allocation + end + end + puts "Read #{lines} number of entries" + rescue => e + require 'debug' + puts "ERROR READING HEAP #{e.message[0, 200]}" + raise e + end + @heap = allocations + + puts "Building reference index" + # References is an index from a referenced object to an array with addresses to the objects that references it + @references = Hash.new { |h, k| h[k] = [] } + REFERENCES = "references".freeze + allocations.each do |k,v| + refs = v[ REFERENCES ] + if refs.is_a?(Array) + refs.each {|addr| @references[ addr.to_i(16) ] << k } + end + end + + @printed = Set.new() + + def print_object(addr, entry) + # only print each node once + return unless @printed.add?(addr) + begin + if addr.is_a?(String) + @output.write( "x#{node_name(addr)} [label=\"#{node_label(addr, entry)}\\n#{addr}\"];\n") + else + @output.write( "x#{node_name(addr)} [label=\"#{node_label(addr, entry)}\\n#{addr.to_s(16)}\"];\n") + end + rescue => e + require 'debug' + raise e + end + end + + def node_label(addr, entry) + if entry[ TYPE ] == OBJECT + class_ref = entry[ "class" ].to_i(16) + @heap[ class_ref ][ "name" ] + elsif entry[ TYPE ] == CLASS + "CLASS #{entry[ "name"]}" + else + entry[TYPE] + end + end + + def node_name(addr) + return addr if addr.is_a? String + addr.to_s(16) + end + + def print_edge(from_addr, to_addr) + @output.write("x#{node_name(from_addr)}->x#{node_name(to_addr)};\n") + end + + def closure_and_edges(diff) + edges = Set.new() + walked = Set.new() + puts "Number of diffs referenced = #{diff.count {|k,_| @references[k].is_a?(Array) && @references[k].size() > 0 }}" + diff.each {|k,_| walk(k, edges, walked) } + edges.each {|e| print_edge(*e) } + end + + def walk(addr, edges, walked) + if !@heap[ addr ].nil? + print_object(addr, @heap[addr]) + + @references [ addr ].each do |r| + walk_to_object(addr, r, edges, walked) + end + end + end + + def walk_to_object(to_addr, cursor, edges, walked) + return unless walked + # if walked to an object, or everything if a single_id is the target + if @heap[ cursor ][ TYPE ] == OBJECT || (@single_id && @heap[ cursor ][ TYPE ] == ROOT_UC || @heap[ cursor ][ TYPE ] == CLASS ) + # and the edge is unique + if edges.add?( [ cursor, to_addr ] ) + # then we may not have visited objects this objects is being referred from + print_object(cursor, @heap[ cursor ]) + # Do not follow what binds a class + if @heap[ cursor ][ TYPE ] != CLASS + @references[ cursor ].each do |r| + walk_to_object(cursor, r, edges, walked.add?(r)) + walked.delete(r) + end + end + end + else + # continue search until Object + @references[cursor].each do |r| + walk_to_object(to_addr, r, edges, walked.add?(r)) + end + end + end + + def single_closure_and_edges(the_target) + edges = Set.new() + walked = Set.new() + walk(the_target, edges, walked) + edges.each {|e| print_edge(*e) } + end + + puts "creating graph" + if @single_id + @output = File.open("memwalk-#{@single_id.to_s(16)}.dot", "w") + @output.write("digraph root {\n") + single_closure_and_edges(@single_id) + else + @output = File.open("memwalk.dot", "w") + @output.write("digraph root {\n") + closure_and_edges(diff_index) + end + @output.write("}\n") + @output.close + puts "done" +end |
