From 319822af6d58c3e0c391e86cfd836ec31de43c67 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Wed, 11 Feb 2009 14:33:48 -0600 Subject: Fixing #1869 - autoloaded files should never leak exceptions Ruby's exception hierarchy is a bit strange, in that only exceptions that sub RuntimeError are caught by default. This patch explicitly catches the base class, Exception, which means that LoadError, SyntaxError, and any RuntimeErrors will all be caught. This is done for both load() and loadall(); load() uses Kernel.load, but loadall() uses Kernel.require. Signed-off-by: Luke Kanies --- CHANGELOG | 2 ++ lib/puppet/util/autoload.rb | 4 ++-- spec/unit/util/autoload.rb | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 2 deletions(-) create mode 100755 spec/unit/util/autoload.rb diff --git a/CHANGELOG b/CHANGELOG index 6b84df04d..42944777f 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ 0.24.8 + Fixing #1869 - autoloaded files should never leak exceptions + Fixing #1543 - Nagios parse errors no longer kill Puppet Fixed #1420 - nagios_serviceescalation not allowing host_name more than one type diff --git a/lib/puppet/util/autoload.rb b/lib/puppet/util/autoload.rb index 535d9ef2e..0c80f8b06 100644 --- a/lib/puppet/util/autoload.rb +++ b/lib/puppet/util/autoload.rb @@ -78,7 +78,7 @@ class Puppet::Util::Autoload name = symbolize(name) loaded name, file return true - rescue LoadError => detail + rescue Exception => detail # I have no idea what's going on here, but different versions # of ruby are raising different errors on missing files. unless detail.to_s =~ /^no such file/i @@ -115,7 +115,7 @@ class Puppet::Util::Autoload begin Kernel.require file loaded(name, file) - rescue => detail + rescue Exception => detail if Puppet[:trace] puts detail.backtrace end diff --git a/spec/unit/util/autoload.rb b/spec/unit/util/autoload.rb new file mode 100755 index 000000000..ff717d6c5 --- /dev/null +++ b/spec/unit/util/autoload.rb @@ -0,0 +1,39 @@ +#!/usr/bin/env ruby + +Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? require(f) : Dir.chdir("..") { s.call(f) } }).call("spec/spec_helper.rb") } + +require 'puppet/util/autoload' + +describe Puppet::Util::Autoload do + before do + @autoload = Puppet::Util::Autoload.new("foo", "tmp") + + @autoload.stubs(:eachdir).yields "/my/dir" + end + + describe "when loading a file" do + [RuntimeError, LoadError, SyntaxError].each do |error| + it "should not die an if a #{error.to_s} exception is thrown" do + FileTest.stubs(:exists?).returns true + + Kernel.expects(:load).raises error + + lambda { @autoload.load("foo") }.should_not raise_error + end + end + end + + describe "when loading all files" do + before do + Dir.stubs(:glob).returns "file.rb" + end + + [RuntimeError, LoadError, SyntaxError].each do |error| + it "should not die an if a #{error.to_s} exception is thrown" do + Kernel.expects(:require).raises error + + lambda { @autoload.loadall }.should_not raise_error + end + end + end +end -- cgit From f0ac3aef53a08e271a5c243f17785cdb58f1f5ef Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Thu, 12 Feb 2009 08:20:43 +1100 Subject: Fixed #1959 - Added column protection for environment schema migration --- CHANGELOG | 2 ++ lib/puppet/rails/database/003_add_environment_to_host.rb | 8 ++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 42944777f..9da1919d8 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ 0.24.8 + Fixed #1959 - Added column protection for environment schema migration + Fixing #1869 - autoloaded files should never leak exceptions Fixing #1543 - Nagios parse errors no longer kill Puppet diff --git a/lib/puppet/rails/database/003_add_environment_to_host.rb b/lib/puppet/rails/database/003_add_environment_to_host.rb index 4593a06f7..3ed10e946 100644 --- a/lib/puppet/rails/database/003_add_environment_to_host.rb +++ b/lib/puppet/rails/database/003_add_environment_to_host.rb @@ -1,9 +1,13 @@ class AddEnvironmentToHost < ActiveRecord::Migration def self.up - add_column :hosts, :environment, :string + unless ActiveRecord::Base.connection.columns(:hosts).collect {|c| c.name}.include?("environment") + add_column :hosts, :environment, :string + end end def self.down - remove_column :hosts, :environment + if ActiveRecord::Base.connection.columns(:hosts).collect {|c| c.name}.include?("environment") + remove_column :hosts, :environment + end end end -- cgit From af3f3ae93ba1e43ac8231e4cf4e5a1f0ed8f3acb Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Wed, 11 Feb 2009 16:49:40 -0600 Subject: Refactoring the XMLRPC::Client error-handling I split it all into smaller, manageable chunks, and used methods for each step, instead of having one huge call. Note that I made all of the tests first, then refactored the code, so I'm confident there's no behavior change. I don't know that this is actually a lot cleaner, but it seems that way to me. I'm open to skipping this, but I think it makes the whole thing a lot cleaner. Signed-off-by: Luke Kanies --- lib/puppet/network/xmlrpc/client.rb | 164 ++++++++++++++++++++++++------------ spec/unit/network/xmlrpc/client.rb | 150 +++++++++++++++++++++++++++++++-- 2 files changed, 254 insertions(+), 60 deletions(-) diff --git a/lib/puppet/network/xmlrpc/client.rb b/lib/puppet/network/xmlrpc/client.rb index 37ace2101..91bb03e1f 100644 --- a/lib/puppet/network/xmlrpc/client.rb +++ b/lib/puppet/network/xmlrpc/client.rb @@ -36,57 +36,7 @@ module Puppet::Network interface.methods.each { |ary| method = ary[0] newclient.send(:define_method,method) { |*args| - Puppet.debug "Calling %s.%s" % [namespace, method] - begin - call("%s.%s" % [namespace, method.to_s],*args) - rescue OpenSSL::SSL::SSLError => detail - if detail.message =~ /bad write retry/ - Puppet.warning "Transient SSL write error; restarting connection and retrying" - self.recycle_connection - retry - end - ["certificate verify failed", "hostname was not match", "hostname not match"].each do |str| - if detail.message.include?(str) - Puppet.warning "Certificate validation failed; consider using the certname configuration option" - end - end - raise XMLRPCClientError, - "Certificates were not trusted: %s" % detail - rescue ::XMLRPC::FaultException => detail - raise XMLRPCClientError, detail.faultString - rescue Errno::ECONNREFUSED => detail - msg = "Could not connect to %s on port %s" % - [@host, @port] - raise XMLRPCClientError, msg - rescue SocketError => detail - Puppet.err "Could not find server %s: %s" % - [@host, detail.to_s] - error = XMLRPCClientError.new( - "Could not find server %s" % @host - ) - error.set_backtrace detail.backtrace - raise error - rescue Errno::EPIPE, EOFError - Puppet.warning "Other end went away; restarting connection and retrying" - self.recycle_connection - retry - rescue Timeout::Error => detail - Puppet.err "Connection timeout calling %s.%s: %s" % - [namespace, method, detail.to_s] - error = XMLRPCClientError.new("Connection Timeout") - error.set_backtrace(detail.backtrace) - raise error - rescue => detail - if detail.message =~ /^Wrong size\. Was \d+, should be \d+$/ - Puppet.warning "XMLRPC returned wrong size. Retrying." - retry - end - Puppet.err "Could not call %s.%s: %s" % - [namespace, method, detail.inspect] - error = XMLRPCClientError.new(detail.to_s) - error.set_backtrace detail.backtrace - raise error - end + make_rpc_call(namespace, method, *args) } } @@ -97,13 +47,117 @@ module Puppet::Network @clients[handler] || self.mkclient(handler) end + class ErrorHandler + def initialize(&block) + metaclass.define_method(:execute, &block) + end + end + + # Use a class variable so all subclasses have access to it. + @@error_handlers = {} + + def self.error_handler(exception) + if handler = @@error_handlers[exception.class] + return handler + else + return @@error_handlers[:default] + end + end + + def self.handle_error(*exceptions, &block) + handler = ErrorHandler.new(&block) + + exceptions.each do |exception| + @@error_handlers[exception] = handler + end + end + + handle_error(OpenSSL::SSL::SSLError) do |client, detail, namespace, method| + if detail.message =~ /bad write retry/ + Puppet.warning "Transient SSL write error; restarting connection and retrying" + client.recycle_connection + return :retry + end + ["certificate verify failed", "hostname was not match", "hostname not match"].each do |str| + if detail.message.include?(str) + Puppet.warning "Certificate validation failed; consider using the certname configuration option" + end + end + raise XMLRPCClientError, "Certificates were not trusted: %s" % detail + end + + handle_error(:default) do |client, detail, namespace, method| + if detail.message.to_s =~ /^Wrong size\. Was \d+, should be \d+$/ + Puppet.warning "XMLRPC returned wrong size. Retrying." + return :retry + end + Puppet.err "Could not call %s.%s: %s" % [namespace, method, detail.inspect] + error = XMLRPCClientError.new(detail.to_s) + error.set_backtrace detail.backtrace + raise error + end + + handle_error(OpenSSL::SSL::SSLError) do |client, detail, namespace, method| + if detail.message =~ /bad write retry/ + Puppet.warning "Transient SSL write error; restarting connection and retrying" + client.recycle_connection + return :retry + end + ["certificate verify failed", "hostname was not match", "hostname not match"].each do |str| + if detail.message.include?(str) + Puppet.warning "Certificate validation failed; consider using the certname configuration option" + end + end + raise XMLRPCClientError, "Certificates were not trusted: %s" % detail + end + + handle_error(::XMLRPC::FaultException) do |client, detail, namespace, method| + raise XMLRPCClientError, detail.faultString + end + + handle_error(Errno::ECONNREFUSED) do |client, detail, namespace, method| + msg = "Could not connect to %s on port %s" % [client.host, client.port] + raise XMLRPCClientError, msg + end + + handle_error(SocketError) do |client, detail, namespace, method| + Puppet.err "Could not find server %s: %s" % [@host, detail.to_s] + error = XMLRPCClientError.new("Could not find server %s" % client.host) + error.set_backtrace detail.backtrace + raise error + end + + handle_error(Errno::EPIPE, EOFError) do |client, detail, namespace, method| + Puppet.warning "Other end went away; restarting connection and retrying" + client.recycle_connection + return :retry + end + + handle_error(Timeout::Error) do |client, detail, namespace, method| + Puppet.err "Connection timeout calling %s.%s: %s" % [namespace, method, detail.to_s] + error = XMLRPCClientError.new("Connection Timeout") + error.set_backtrace(detail.backtrace) + raise error + end + + def make_rpc_call(namespace, method, *args) + Puppet.debug "Calling %s.%s" % [namespace, method] + begin + call("%s.%s" % [namespace, method.to_s],*args) + rescue Exception => detail + retry if self.class.error_handler(detail).execute(self, detail, namespace, method) == :retry + end + end + def http unless @http - @http = Puppet::Network::HttpPool.http_instance(@host, @port, true) + @http = Puppet::Network::HttpPool.http_instance(host, port, true) end @http end + attr_reader :host, :port + def initialize(hash = {}) hash[:Path] ||= "/RPC2" hash[:Server] ||= Puppet[:server] @@ -135,7 +189,11 @@ module Puppet::Network # or we've just downloaded certs and we need to create new http instances # with the certs added. def recycle_connection - @http = Puppet::Network::HttpPool.http_instance(@host, @port, true) # reset the instance + if http.started? + http.finish + end + @http = nil + self.http # force a new one end def start diff --git a/spec/unit/network/xmlrpc/client.rb b/spec/unit/network/xmlrpc/client.rb index a0a2e77fb..76ef5c799 100755 --- a/spec/unit/network/xmlrpc/client.rb +++ b/spec/unit/network/xmlrpc/client.rb @@ -2,12 +2,148 @@ Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? require(f) : Dir.chdir("..") { s.call(f) } }).call("spec/spec_helper.rb") } -describe Puppet::Network do - it "should raise an XMLRPCClientError if a generated class raises a Timeout::Error" do - http = mock 'http' - Puppet::Network::HttpPool.stubs(:http_instance).returns http - file = Puppet::Network::Client.file.new({:Server => "foo.com"}) - http.stubs(:post2).raises Timeout::Error - lambda { file.retrieve }.should raise_error(Puppet::Network::XMLRPCClientError) +describe Puppet::Network::XMLRPCClient do + describe "when performing the rpc call" do + before do + @client = Puppet::Network::Client.report.xmlrpc_client.new + @client.stubs(:call).returns "foo" + + end + + it "should call the specified namespace and method, with the specified arguments" do + @client.expects(:call).with("puppetreports.report", "eh").returns "foo" + @client.report("eh") + end + + it "should return the results from the call" do + @client.expects(:call).returns "foo" + @client.report("eh").should == "foo" + end + + describe "when returning the http instance" do + it "should use the http pool to create the instance" do + @client.instance_variable_set("@http", nil) + @client.expects(:host).returns "myhost" + @client.expects(:port).returns "myport" + Puppet::Network::HttpPool.expects(:http_instance).with("myhost", "myport", true).returns "http" + + @client.http.should == "http" + end + + it "should reuse existing instances" do + @client.http.should equal(@client.http) + end + end + + describe "when recycling the connection" do + it "should close the existing instance if it's open" do + http = mock 'http' + @client.stubs(:http).returns http + + http.expects(:started?).returns true + http.expects(:finish) + + @client.recycle_connection + end + + it "should force creation of a new instance" do + Puppet::Network::HttpPool.expects(:http_instance).returns "second_http" + + @client.recycle_connection + + @client.http.should == "second_http" + end + end + + describe "and an exception is raised" do + it "should raise XMLRPCClientError if XMLRPC::FaultException is raised" do + error = XMLRPC::FaultException.new("foo", "bar") + + @client.expects(:call).raises(error) + + lambda { @client.report("eh") }.should raise_error(Puppet::Network::XMLRPCClientError) + end + + it "should raise XMLRPCClientError if Errno::ECONNREFUSED is raised" do + @client.expects(:call).raises(Errno::ECONNREFUSED) + + lambda { @client.report("eh") }.should raise_error(Puppet::Network::XMLRPCClientError) + end + + it "should log and raise XMLRPCClientError if Timeout::Error is raised" do + Puppet.expects(:err) + @client.expects(:call).raises(Timeout::Error) + + lambda { @client.report("eh") }.should raise_error(Puppet::Network::XMLRPCClientError) + end + + it "should log and raise XMLRPCClientError if SocketError is raised" do + Puppet.expects(:err) + @client.expects(:call).raises(SocketError) + + lambda { @client.report("eh") }.should raise_error(Puppet::Network::XMLRPCClientError) + end + + it "should log, recycle the connection, and retry if Errno::EPIPE is raised" do + @client.expects(:call).times(2).raises(Errno::EPIPE).then.returns "eh" + + Puppet.expects(:warning) + @client.expects(:recycle_connection) + + @client.report("eh") + end + + it "should log, recycle the connection, and retry if EOFError is raised" do + @client.expects(:call).times(2).raises(EOFError).then.returns "eh" + + Puppet.expects(:warning) + @client.expects(:recycle_connection) + + @client.report("eh") + end + + it "should log and retry if an exception containing 'Wrong size' is raised" do + error = RuntimeError.new("Wrong size. Was 15, should be 30") + @client.expects(:call).times(2).raises(error).then.returns "eh" + + Puppet.expects(:warning) + + @client.report("eh") + end + + it "should raise XMLRPCClientError if OpenSSL::SSL::SSLError is raised" do + @client.expects(:call).raises(OpenSSL::SSL::SSLError) + + lambda { @client.report("eh") }.should raise_error(Puppet::Network::XMLRPCClientError) + end + + it "should log and raise XMLRPCClientError if OpenSSL::SSL::SSLError is raised with certificate issues" do + error = OpenSSL::SSL::SSLError.new("hostname was not match") + @client.expects(:call).raises(error) + + Puppet.expects(:warning) + + lambda { @client.report("eh") }.should raise_error(Puppet::Network::XMLRPCClientError) + end + + it "should log, recycle the connection, and retry if OpenSSL::SSL::SSLError is raised containing 'bad write retry'" do + error = OpenSSL::SSL::SSLError.new("bad write retry") + @client.expects(:call).times(2).raises(error).then.returns "eh" + + @client.expects(:recycle_connection) + + Puppet.expects(:warning) + + @client.report("eh") + end + + it "should log and raise XMLRPCClientError if any other exception is raised" do + @client.expects(:call).raises(RuntimeError) + + Puppet.expects(:err) + + lambda { @client.report("eh") }.should raise_error(Puppet::Network::XMLRPCClientError) + end + end end end -- cgit From 5e35166f1b7219d470916d1ee7a4e6852afaf1f9 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Wed, 11 Feb 2009 16:54:04 -0600 Subject: Fixing #961 - closing the http connection after every xmlrpc call There were apparently some circumstances that resulted in the connection not being closed; this just closes it every time if it's still open after the rpc call is complete. Signed-off-by: Luke Kanies --- CHANGELOG | 4 ++++ lib/puppet/network/xmlrpc/client.rb | 4 +++- spec/unit/network/xmlrpc/client.rb | 22 ++++++++++++++++++++++ 3 files changed, 29 insertions(+), 1 deletion(-) diff --git a/CHANGELOG b/CHANGELOG index 9da1919d8..1eb84a02c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,8 @@ 0.24.8 + Fixing #944 - changing error message from warning to info - connection recycled + + Fixed #961 - puppetd creating too many/not closing TCP connections + Fixed #1959 - Added column protection for environment schema migration Fixing #1869 - autoloaded files should never leak exceptions diff --git a/lib/puppet/network/xmlrpc/client.rb b/lib/puppet/network/xmlrpc/client.rb index 91bb03e1f..678ab6c00 100644 --- a/lib/puppet/network/xmlrpc/client.rb +++ b/lib/puppet/network/xmlrpc/client.rb @@ -128,7 +128,7 @@ module Puppet::Network end handle_error(Errno::EPIPE, EOFError) do |client, detail, namespace, method| - Puppet.warning "Other end went away; restarting connection and retrying" + Puppet.info "Other end went away; restarting connection and retrying" client.recycle_connection return :retry end @@ -147,6 +147,8 @@ module Puppet::Network rescue Exception => detail retry if self.class.error_handler(detail).execute(self, detail, namespace, method) == :retry end + ensure + http.finish if http.started? end def http diff --git a/spec/unit/network/xmlrpc/client.rb b/spec/unit/network/xmlrpc/client.rb index 76ef5c799..36e59429c 100755 --- a/spec/unit/network/xmlrpc/client.rb +++ b/spec/unit/network/xmlrpc/client.rb @@ -20,6 +20,28 @@ describe Puppet::Network::XMLRPCClient do @client.report("eh").should == "foo" end + it "should always close the http connection if it is still open after the call" do + http = mock 'http' + @client.stubs(:http).returns http + + http.expects(:started?).returns true + http.expects(:finish) + + @client.report("eh").should == "foo" + end + + it "should always close the http connection if it is still open after a call that raises an exception" do + http = mock 'http' + @client.stubs(:http).returns http + + @client.expects(:call).raises RuntimeError + + http.expects(:started?).returns true + http.expects(:finish) + + lambda { @client.report("eh") }.should raise_error + end + describe "when returning the http instance" do it "should use the http pool to create the instance" do @client.instance_variable_set("@http", nil) -- cgit From cc4d6586d420f4beea1eeef85cfe7a28f8e493ac Mon Sep 17 00:00:00 2001 From: Bryan Kearney Date: Thu, 12 Feb 2009 09:32:01 -0500 Subject: Bug #1948: Added patch by jab to support the correct ins syntax. Updated the test cases as well --- CHANGELOG | 2 ++ lib/puppet/provider/augeas/augeas.rb | 32 ++++++++++++++++++++++++++------ spec/unit/provider/augeas/augeas.rb | 25 +++++++++++++------------ 3 files changed, 41 insertions(+), 18 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 1eb84a02c..329e700f7 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ 0.24.8 + Fixing #1948 and #1953 - augeas ins bug: wrong number of arguments (1 for 3) + Fixing #944 - changing error message from warning to info - connection recycled Fixed #961 - puppetd creating too many/not closing TCP connections diff --git a/lib/puppet/provider/augeas/augeas.rb b/lib/puppet/provider/augeas/augeas.rb index 0ffddebb0..fd47da389 100644 --- a/lib/puppet/provider/augeas/augeas.rb +++ b/lib/puppet/provider/augeas/augeas.rb @@ -164,14 +164,34 @@ Puppet::Type.type(:augeas).provide(:augeas) do fail("invalid command #{cmd_array.join[" "]}") if cmd_array.length < 2 command = cmd_array[0] cmd_array.shift() - cmd_array[0]=File.join(context, cmd_array[0]) - debug("sending command '#{command}' with params #{cmd_array.inspect}") begin case command - when "set": aug.set(cmd_array[0], cmd_array[1]) - when "rm", "remove": aug.rm(cmd_array[0]) - when "clear": aug.clear(cmd_array[0]) - when "insert", "ins": aug.insert(cmd_array[0]) + when "set": + cmd_array[0]=File.join(context, cmd_array[0]) + debug("sending command '#{command}' with params #{cmd_array.inspect}") + aug.set(cmd_array[0], cmd_array[1]) + when "rm", "remove": + cmd_array[0]=File.join(context, cmd_array[0]) + debug("sending command '#{command}' with params #{cmd_array.inspect}") + aug.rm(cmd_array[0]) + when "clear": + cmd_array[0]=File.join(context, cmd_array[0]) + debug("sending command '#{command}' with params #{cmd_array.inspect}") + aug.clear(cmd_array[0]) + when "insert", "ins" + if cmd_array.size < 3 + fail("ins requires 3 parameters") + end + label = cmd_array[0] + where = cmd_array[1] + path = File.join(context, cmd_array[2]) + case where + when "before": before = true + when "after": before = false + else fail("Invalid value '#{where}' for where param") + end + debug("sending command '#{command}' with params #{[label, where, path]}") + aug.insert(path, label, before) else fail("Command '#{command}' is not supported") end rescue Exception => e diff --git a/spec/unit/provider/augeas/augeas.rb b/spec/unit/provider/augeas/augeas.rb index 1bbf18f83..4bc1814e5 100644 --- a/spec/unit/provider/augeas/augeas.rb +++ b/spec/unit/provider/augeas/augeas.rb @@ -206,30 +206,31 @@ describe provider_class do @augeas.expects(:save).returns(true) @provider.execute_changes.should == :executed end - - it "should handle insert commands" do - command = [["insert", "/Jar/Jar"]] + + + it "should handle ins commands with before" do + command = [["ins", "Binks", "before", "/Jar/Jar"]] context = "/foo" @resource.expects(:[]).times(2).returns(command).then.returns(context) - @augeas.expects(:insert).with("/foo/Jar/Jar") + @augeas.expects(:insert).with("/foo/Jar/Jar", "Binks", true) @augeas.expects(:save).returns(true) @provider.execute_changes.should == :executed - end - - it "should handle ins commands" do - command = [["ins", "/Jar/Jar"]] + end + + it "should handle ins commands with before" do + command = [["ins", "Binks", "after", "/Jar/Jar"]] context = "/foo" @resource.expects(:[]).times(2).returns(command).then.returns(context) - @augeas.expects(:insert).with("/foo/Jar/Jar") + @augeas.expects(:insert).with("/foo/Jar/Jar", "Binks", false) @augeas.expects(:save).returns(true) @provider.execute_changes.should == :executed - end + end it "should handle multiple commands" do - command = [["ins", "/Jar/Jar"], ["clear", "/Jar/Jar"]] + command = [["ins", "Binks", "after", "/Jar/Jar"], ["clear", "/Jar/Jar"]] context = "/foo" @resource.expects(:[]).times(2).returns(command).then.returns(context) - @augeas.expects(:insert).with("/foo/Jar/Jar") + @augeas.expects(:insert).with("/foo/Jar/Jar", "Binks", false) @augeas.expects(:clear).with("/foo/Jar/Jar") @augeas.expects(:save).returns(true) @provider.execute_changes.should == :executed -- cgit From 4e89156b21b287b683c27a4cd691856c4e378a62 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Thu, 15 Jan 2009 15:13:28 -0600 Subject: Migrated FileType tests to spec, and fleshed them out a bit. Signed-off-by: Luke Kanies --- lib/puppet/util/filetype.rb | 6 +-- spec/unit/util/filetype.rb | 116 ++++++++++++++++++++++++++++++++++++++++++++ test/util/filetype.rb | 91 ---------------------------------- 3 files changed, 119 insertions(+), 94 deletions(-) create mode 100644 spec/unit/util/filetype.rb diff --git a/lib/puppet/util/filetype.rb b/lib/puppet/util/filetype.rb index 60cbc77e7..60380d5f1 100755 --- a/lib/puppet/util/filetype.rb +++ b/lib/puppet/util/filetype.rb @@ -74,7 +74,7 @@ class Puppet::Util::FileType # Back the file up before replacing it. def backup - bucket.backup(@path) if FileTest.exists?(@path) + bucket.backup(@path) if File.exists?(@path) end # Pick or create a filebucket to use. @@ -92,7 +92,7 @@ class Puppet::Util::FileType newfiletype(:flat) do # Read the file. def read - if File.exists?(@path) + if File.exist?(@path) File.read(@path) else return nil @@ -101,7 +101,7 @@ class Puppet::Util::FileType # Remove the file. def remove - if File.exists?(@path) + if File.exist?(@path) File.unlink(@path) end end diff --git a/spec/unit/util/filetype.rb b/spec/unit/util/filetype.rb new file mode 100644 index 000000000..74dae3356 --- /dev/null +++ b/spec/unit/util/filetype.rb @@ -0,0 +1,116 @@ +#!/usr/bin/env ruby + +Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? require(f) : Dir.chdir("..") { s.call(f) } }).call("spec/spec_helper.rb") } + +require 'puppet/util/filetype' + +# XXX Import all of the tests into this file. +describe Puppet::Util::FileType do + describe "when backing up a file" do + before do + @file = Puppet::Util::FileType.filetype(:flat).new("/my/file") + end + + it "should do nothing if the file does not exist" do + File.expects(:exists?).with("/my/file").returns false + @file.expects(:bucket).never + @file.backup + end + + it "should use its filebucket to backup the file if it exists" do + File.expects(:exists?).with("/my/file").returns true + + bucket = mock 'bucket' + bucket.expects(:backup).with("/my/file") + + @file.expects(:bucket).returns bucket + @file.backup + end + + it "should use the filebucket named 'puppet' if it finds one" do + bucket = mock 'bucket' + bucket.expects(:bucket).returns "mybucket" + + Puppet::Type.type(:filebucket).expects(:[]).with("puppet").returns bucket + + @file.bucket.should == "mybucket" + end + + it "should use the default filebucket if none named 'puppet' is found" do + bucket = mock 'bucket' + bucket.expects(:bucket).returns "mybucket" + + Puppet::Type.type(:filebucket).expects(:[]).with("puppet").returns nil + Puppet::Type.type(:filebucket).expects(:mkdefaultbucket).returns bucket + + @file.bucket.should == "mybucket" + end + end + + describe "the flat filetype" do + before do + @type = Puppet::Util::FileType.filetype(:flat) + end + it "should exist" do + @type.should_not be_nil + end + + describe "when the file already exists" do + it "should return the file's contents when asked to read it" do + file = @type.new("/my/file") + File.expects(:exist?).with("/my/file").returns true + File.expects(:read).with("/my/file").returns "my text" + + file.read.should == "my text" + end + + it "should unlink the file when asked to remove it" do + file = @type.new("/my/file") + File.expects(:exist?).with("/my/file").returns true + File.expects(:unlink).with("/my/file") + + file.remove + end + end + + describe "when the file does not exist" do + it "should return an empty string when asked to read the file" do + file = @type.new("/my/file") + File.expects(:exist?).with("/my/file").returns false + + file.read.should == "" + end + end + + describe "when writing the file" do + before do + @file = @type.new("/my/file") + FileUtils.stubs(:cp) + + @tempfile = stub 'tempfile', :print => nil, :close => nil, :flush => nil, :path => "/other/file" + Tempfile.stubs(:new).returns @tempfile + end + + it "should back up the file" do + @file.expects(:backup) + + @file.write("foo") + end + + it "should first create a temp file and copy its contents over to the file location" do + Tempfile.expects(:new).with("puppet").returns @tempfile + @tempfile.expects(:print).with("my text") + @tempfile.expects(:flush) + @tempfile.expects(:close) + FileUtils.expects(:cp).with(@tempfile.path, "/my/file") + + @file.write "my text" + end + + it "should set the selinux default context on the file" do + @file.expects(:set_selinux_default_context).with("/my/file") + @file.write "eh" + end + end + end +end diff --git a/test/util/filetype.rb b/test/util/filetype.rb index 6c7ede07b..5b01a8bb4 100755 --- a/test/util/filetype.rb +++ b/test/util/filetype.rb @@ -8,97 +8,6 @@ require 'mocha' class TestFileType < Test::Unit::TestCase include PuppetTest - - def test_flat - obj = nil - path = tempfile() - type = nil - - assert_nothing_raised { - type = Puppet::Util::FileType.filetype(:flat) - } - - assert(type, "Could not retrieve flat filetype") - - assert_nothing_raised { - obj = type.new(path) - } - - text = "This is some text\n" - - newtext = nil - assert_nothing_raised { - newtext = obj.read - } - - # The base class doesn't allow a return of nil - assert_equal("", newtext, "Somehow got some text") - - assert_nothing_raised { - obj.write(text) - } - assert_nothing_raised { - newtext = obj.read - } - - assert_equal(text, newtext, "Text was changed somehow") - - File.open(path, "w") { |f| f.puts "someyayness" } - - text = File.read(path) - assert_nothing_raised { - newtext = obj.read - } - - assert_equal(text, newtext, "Text was changed somehow") - end - - # Make sure that modified files are backed up before they're changed. - def test_backup_is_called - path = tempfile - File.open(path, "w") { |f| f.print 'yay' } - - obj = Puppet::Util::FileType.filetype(:flat).new(path) - - obj.expects(:backup) - - obj.write("something") - - assert_equal("something", File.read(path), "File did not get changed") - end - - def test_backup - path = tempfile - type = Puppet::Type.type(:filebucket) - - obj = Puppet::Util::FileType.filetype(:flat).new(path) - - # First try it when the file does not yet exist. - assert_nothing_raised("Could not call backup when file does not exist") do - obj.backup - end - - # Then create the file - File.open(path, "w") { |f| f.print 'one' } - - # Then try it with no filebucket objects - assert_nothing_raised("Could not call backup with no buckets") do - obj.backup - end - puppet = type["puppet"] - assert(puppet, "Did not create default filebucket") - - assert_equal("one", puppet.bucket.getfile(Digest::MD5.hexdigest(File.read(path))), "Could not get file from backup") - - # Try it again when the default already exists - File.open(path, "w") { |f| f.print 'two' } - assert_nothing_raised("Could not call backup with no buckets") do - obj.backup - end - - assert_equal("two", puppet.bucket.getfile(Digest::MD5.hexdigest(File.read(path))), "Could not get file from backup") - end - if Facter["operatingsystem"].value == "Darwin" def test_ninfotoarray obj = nil -- cgit From 53f15b9208a3271e944c687b8d0e670d422fb832 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Thu, 15 Jan 2009 15:42:09 -0600 Subject: Removing the apparently obsolete netinfo filetype. Signed-off-by: Luke Kanies --- lib/puppet/util/filetype.rb | 88 --------------------------------------------- test/util/filetype.rb | 46 ------------------------ 2 files changed, 134 deletions(-) delete mode 100755 test/util/filetype.rb diff --git a/lib/puppet/util/filetype.rb b/lib/puppet/util/filetype.rb index 60380d5f1..137ef0c67 100755 --- a/lib/puppet/util/filetype.rb +++ b/lib/puppet/util/filetype.rb @@ -250,92 +250,4 @@ class Puppet::Util::FileType output_file.delete end end - - # Treat netinfo tables as a single file, just for simplicity of certain - # types - newfiletype(:netinfo) do - class << self - attr_accessor :format - end - def read - %x{nidump -r /#{@path} /} - end - - # This really only makes sense for cron tabs. - def remove - %x{nireport / /#{@path} name}.split("\n").each do |name| - newname = name.gsub(/\//, '\/').sub(/\s+$/, '') - output = %x{niutil -destroy / '/#{@path}/#{newname}'} - - unless $? == 0 - raise Puppet::Error, "Could not remove %s from %s" % - [name, @path] - end - end - end - - # Convert our table to an array of hashes. This only works for - # handling one table at a time. - def to_array(text = nil) - unless text - text = read - end - - hash = nil - - # Initialize it with the first record - records = [] - text.split("\n").each do |line| - next if line =~ /^[{}]$/ # Skip the wrapping lines - next if line =~ /"name" = \( "#{@path}" \)/ # Skip the table name - next if line =~ /CHILDREN = \(/ # Skip this header - next if line =~ /^ \)/ # and its closer - - # Now we should have nothing but records, wrapped in braces - - case line - when /^\s+\{/: hash = {} - when /^\s+\}/: records << hash - when /\s+"(\w+)" = \( (.+) \)/ - field = $1 - values = $2 - - # Always use an array - hash[field] = [] - - values.split(/, /).each do |value| - if value =~ /^"(.*)"$/ - hash[field] << $1 - else - raise ArgumentError, "Could not match value %s" % value - end - end - else - raise ArgumentError, "Could not match line %s" % line - end - end - - records - end - - def write(text) - text.gsub!(/^#.*\n/,'') - text.gsub!(/^$/,'') - if text == "" or text == "\n" - self.remove - return - end - unless format = self.class.format - raise Puppe::DevError, "You must define the NetInfo format to inport" - end - IO.popen("niload -d #{format} . 1>/dev/null 2>/dev/null", "w") { |p| - p.print text - } - - unless $? == 0 - raise ArgumentError, "Failed to write %s" % @path - end - end - end end - diff --git a/test/util/filetype.rb b/test/util/filetype.rb deleted file mode 100755 index 5b01a8bb4..000000000 --- a/test/util/filetype.rb +++ /dev/null @@ -1,46 +0,0 @@ -#!/usr/bin/env ruby - -require File.dirname(__FILE__) + '/../lib/puppettest' - -require 'puppettest' -require 'puppet/util/filetype' -require 'mocha' - -class TestFileType < Test::Unit::TestCase - include PuppetTest - if Facter["operatingsystem"].value == "Darwin" - def test_ninfotoarray - obj = nil - type = nil - - assert_nothing_raised { - type = Puppet::Util::FileType.filetype(:netinfo) - } - - assert(type, "Could not retrieve netinfo filetype") - %w{users groups aliases}.each do |map| - assert_nothing_raised { - obj = type.new(map) - } - - assert_nothing_raised("could not read map %s" % map) { - obj.read - } - - array = nil - - assert_nothing_raised("Failed to parse %s map" % map) { - array = obj.to_array - } - - assert_instance_of(Array, array) - - array.each do |record| - assert_instance_of(Hash, record) - assert(record.length != 0) - end - end - end - end -end - -- cgit From d5a193a594bbc2194dbf1e5264369ebea0e55880 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Thu, 15 Jan 2009 15:44:09 -0600 Subject: Fixing #1541 - ParsedFile only backs up files once per transaction This moves responsibility for backups from the filetype to the consumer of the filetype, but only ParsedFile actually uses filetypes. Signed-off-by: Luke Kanies --- lib/puppet/provider/parsedfile.rb | 14 ++++++++++++++ lib/puppet/util/filetype.rb | 1 - spec/unit/provider/parsedfile.rb | 36 ++++++++++++++++++++++++++++++++++++ spec/unit/util/filetype.rb | 6 ------ 4 files changed, 50 insertions(+), 7 deletions(-) diff --git a/lib/puppet/provider/parsedfile.rb b/lib/puppet/provider/parsedfile.rb index a4c4bc87c..45eae57ff 100755 --- a/lib/puppet/provider/parsedfile.rb +++ b/lib/puppet/provider/parsedfile.rb @@ -78,8 +78,22 @@ class Puppet::Provider::ParsedFile < Puppet::Provider @modified.reject! { |t| flushed.include?(t) } end + # Make sure our file is backed up, but only back it up once per transaction. + # We cheat and rely on the fact that @records is created on each prefetch. + def self.backup_target(target) + unless defined?(@backup_stats) + @backup_stats = {} + end + return nil if @backup_stats[target] == @records.object_id + + target_object(target).backup + @backup_stats[target] = @records.object_id + end + # Flush all of the records relating to a specific target. def self.flush_target(target) + backup_target(target) + records = target_records(target).reject { |r| r[:ensure] == :absent } diff --git a/lib/puppet/util/filetype.rb b/lib/puppet/util/filetype.rb index 137ef0c67..5d4ba1440 100755 --- a/lib/puppet/util/filetype.rb +++ b/lib/puppet/util/filetype.rb @@ -108,7 +108,6 @@ class Puppet::Util::FileType # Overwrite the file. def write(text) - backup() require "tempfile" tf = Tempfile.new("puppet") tf.print text; tf.flush diff --git a/spec/unit/provider/parsedfile.rb b/spec/unit/provider/parsedfile.rb index 05e9de3ab..11a91c8d7 100755 --- a/spec/unit/provider/parsedfile.rb +++ b/spec/unit/provider/parsedfile.rb @@ -47,4 +47,40 @@ describe Puppet::Provider::ParsedFile do @class.instances end end + + describe "when flushing a file's records to disk" do + before do + # This way we start with some @records, like we would in real life. + @class.stubs(:retrieve).returns [] + @class.default_target = "/foo/bar" + @class.initvars + @class.prefetch + + @filetype = mock 'filetype' + Puppet::Util::FileType.filetype(:flat).expects(:new).with("/my/file").returns @filetype + + @filetype.stubs(:write) + end + + it "should back up the file being written" do + @filetype.expects(:backup) + + @class.flush_target("/my/file") + end + + it "should not back up the file more than once between calls to 'prefetch'" do + @filetype.expects(:backup).once + + @class.flush_target("/my/file") + @class.flush_target("/my/file") + end + + it "should back the file up again once the file has been reread" do + @filetype.expects(:backup).times(2) + + @class.flush_target("/my/file") + @class.prefetch + @class.flush_target("/my/file") + end + end end diff --git a/spec/unit/util/filetype.rb b/spec/unit/util/filetype.rb index 74dae3356..0506b6b47 100644 --- a/spec/unit/util/filetype.rb +++ b/spec/unit/util/filetype.rb @@ -91,12 +91,6 @@ describe Puppet::Util::FileType do Tempfile.stubs(:new).returns @tempfile end - it "should back up the file" do - @file.expects(:backup) - - @file.write("foo") - end - it "should first create a temp file and copy its contents over to the file location" do Tempfile.expects(:new).with("puppet").returns @tempfile @tempfile.expects(:print).with("my text") -- cgit From 4dfa034dc8a569634d8b5672f66158157ddddb28 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Thu, 12 Feb 2009 15:32:42 -0600 Subject: Revert "Refixing #1420 - _naginator_name is only used for services" This reverts commit efb5cc50c42bc27aec9409e723e3a717ed58c0a8. Conflicts: CHANGELOG --- lib/puppet/external/nagios/base.rb | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/lib/puppet/external/nagios/base.rb b/lib/puppet/external/nagios/base.rb index d95b808dd..6a0c1831c 100755 --- a/lib/puppet/external/nagios/base.rb +++ b/lib/puppet/external/nagios/base.rb @@ -416,18 +416,20 @@ class Nagios::Base :dependent_service_description, :host_name, :hostgroup_name, :service_description, :inherits_parent, :execution_failure_criteria, :notification_failure_criteria, :dependency_period, - :register, :use + :register, :use, + :_naginator_name - setnamevar :service_description + setnamevar :_naginator_name end newtype :serviceescalation do setparameters :host_name, :hostgroup_name, :service_description, :contacts, :contact_groups, :first_notification, :last_notification, :notification_interval, :escalation_period, :escalation_options, - :register, :use + :register, :use, + :_naginator_name - setnamevar :service_description + setnamevar :_naginator_name end newtype :hostdependency do @@ -435,18 +437,20 @@ class Nagios::Base setparameters :dependent_host_name, :dependent_hostgroup_name, :host_name, :hostgroup_name, :inherits_parent, :execution_failure_criteria, :notification_failure_criteria, :dependency_period, - :register, :use + :register, :use, + :_naginator_name - setnamevar :host_name + setnamevar :_naginator_name end newtype :hostescalation do setparameters :host_name, :hostgroup_name, :contacts, :contact_groups, :first_notification, :last_notification, :notification_interval, :escalation_period, :escalation_options, - :register, :use + :register, :use, + :_naginator_name - setnamevar :host_name + setnamevar :_naginator_name end newtype :hostextinfo do @@ -463,9 +467,10 @@ class Nagios::Base setparameters :host_name, :service_description, :notes, :notes_url, :action_url, :icon_image, :icon_image_alt, - :register, :use + :register, :use, + :_naginator_name - setnamevar :service_description + setnamevar :_naginator_name end end -- cgit From 70ea39af65f7e56ce151c9f9ca444261d0332454 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Thu, 12 Feb 2009 16:00:29 -0600 Subject: Adding a post-processor for Nagios names. This is a brutal hack until Puppet correctly supports multiple primary keys. It basically just comments out _naginator_name before writing to disk, and uncomments it when reading. This allows Puppet to use it while Nagios ignores it. Yes, a stupid hack, but it appears to work. Signed-off-by: Luke Kanies --- lib/puppet/provider/naginator.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/puppet/provider/naginator.rb b/lib/puppet/provider/naginator.rb index 233d82eb6..5510eb9c8 100644 --- a/lib/puppet/provider/naginator.rb +++ b/lib/puppet/provider/naginator.rb @@ -7,6 +7,7 @@ require 'puppet/external/nagios' # The base class for all Naginator providers. class Puppet::Provider::Naginator < Puppet::Provider::ParsedFile + NAME_STRING = "## --PUPPET_NAME-- (called '_naginator_name' in the manifest)" # Retrieve the associated class from Nagios::Base. def self.nagios_type unless defined?(@nagios_type) and @nagios_type @@ -24,14 +25,14 @@ class Puppet::Provider::Naginator < Puppet::Provider::ParsedFile def self.parse(text) begin - Nagios::Parser.new.parse(text) + Nagios::Parser.new.parse(text.gsub(NAME_STRING, "_naginator_name")) rescue => detail raise Puppet::Error, "Could not parse configuration for %s: %s" % [resource_type.name, detail] end end def self.to_file(records) - header + records.collect { |record| record.to_s }.join("\n") + header + records.collect { |record| record.to_s }.join("\n").gsub("_naginator_name", NAME_STRING) end def self.skip_record?(record) -- cgit From 7cf085c9ebdb1d1c92e317a406844bfc0eceafe6 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Thu, 12 Feb 2009 22:48:34 -0600 Subject: Adding tests for Puppet::Indirector::Facts::Facter.loadfacts I just copied the tests from the master branch, changed as necessary. Signed-off-by: Luke Kanies --- spec/unit/indirector/facts/facter.rb | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/spec/unit/indirector/facts/facter.rb b/spec/unit/indirector/facts/facter.rb index 225eb153b..5dcc4442b 100755 --- a/spec/unit/indirector/facts/facter.rb +++ b/spec/unit/indirector/facts/facter.rb @@ -70,7 +70,14 @@ describe Puppet::Node::Facts::Facter do end end - describe Puppet::Node::Facts::Facter, " when loading facts from the factpath" do - it "should load every fact in each factpath directory" + describe Puppet::Node::Facts::Facter, "when loading facts from the factpath" do + it "should load each directory in the Fact path when loading fact" do + Puppet.settings.expects(:value).with(:factpath).returns("one%stwo" % File::PATH_SEPARATOR) + + Puppet::Node::Facts::Facter.expects(:loaddir).with("one", "fact") + Puppet::Node::Facts::Facter.expects(:loaddir).with("two", "fact") + + Puppet::Node::Facts::Facter.loadfacts + end end end -- cgit From 39a8b28690377339d4c430ebf62cec5ef0ed34b8 Mon Sep 17 00:00:00 2001 From: Luke Kanies Date: Thu, 12 Feb 2009 22:58:57 -0600 Subject: Fixing #1964 - Facts get loaded from plugins Applying slightly modified patch. Also added tests. Signed-off-by: Luke Kanies --- lib/puppet/indirector/facts/facter.rb | 8 ++++++-- spec/unit/indirector/facts/facter.rb | 22 ++++++++++++++++++++-- 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/lib/puppet/indirector/facts/facter.rb b/lib/puppet/indirector/facts/facter.rb index 6ed89dac1..a026dfe60 100644 --- a/lib/puppet/indirector/facts/facter.rb +++ b/lib/puppet/indirector/facts/facter.rb @@ -24,8 +24,12 @@ class Puppet::Node::Facts::Facter < Puppet::Indirector::Code end def self.loadfacts - # LAK:NOTE See http://snurl.com/21zf8 [groups_google_com] - x = Puppet[:factpath].split(":").each do |dir| + # Add any per-module fact directories to the factpath + module_fact_dirs = Puppet[:modulepath].split(":").collect do |d| + Dir.glob("%s/*/plugins/facter" % d) + end.flatten + dirs = module_fact_dirs + Puppet[:factpath].split(":") + x = dirs.each do |dir| loaddir(dir, "fact") end end diff --git a/spec/unit/indirector/facts/facter.rb b/spec/unit/indirector/facts/facter.rb index 5dcc4442b..530e5a752 100755 --- a/spec/unit/indirector/facts/facter.rb +++ b/spec/unit/indirector/facts/facter.rb @@ -70,8 +70,9 @@ describe Puppet::Node::Facts::Facter do end end - describe Puppet::Node::Facts::Facter, "when loading facts from the factpath" do - it "should load each directory in the Fact path when loading fact" do + describe Puppet::Node::Facts::Facter, "when loading facts from disk" do + it "should load each directory in the Fact path" do + Puppet.settings.stubs(:value).returns "foo" Puppet.settings.expects(:value).with(:factpath).returns("one%stwo" % File::PATH_SEPARATOR) Puppet::Node::Facts::Facter.expects(:loaddir).with("one", "fact") @@ -79,5 +80,22 @@ describe Puppet::Node::Facts::Facter do Puppet::Node::Facts::Facter.loadfacts end + + it "should load all facts from the modules" do + Puppet.settings.stubs(:value).returns "foo" + Puppet::Node::Facts::Facter.stubs(:loaddir) + + Puppet.settings.expects(:value).with(:modulepath).returns("one%stwo" % File::PATH_SEPARATOR) + + Dir.expects(:glob).with("one/*/plugins/facter").returns %w{oneA oneB} + Dir.expects(:glob).with("two/*/plugins/facter").returns %w{twoA twoB} + + Puppet::Node::Facts::Facter.expects(:loaddir).with("oneA", "fact") + Puppet::Node::Facts::Facter.expects(:loaddir).with("oneB", "fact") + Puppet::Node::Facts::Facter.expects(:loaddir).with("twoA", "fact") + Puppet::Node::Facts::Facter.expects(:loaddir).with("twoB", "fact") + + Puppet::Node::Facts::Facter.loadfacts + end end end -- cgit From 2218611f7fa10b59945f65c80b05cbd8ebe137ef Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Fri, 13 Feb 2009 17:04:36 +1100 Subject: Updated up2date and service confines to add support for Oracle EL and VM --- CHANGELOG | 2 ++ lib/puppet/provider/package/up2date.rb | 6 ++++-- lib/puppet/provider/service/redhat.rb | 2 +- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 329e700f7..a8e828793 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ 0.24.8 + Updated up2date and service confines to add support for Oracle EL and VM + Fixing #1948 and #1953 - augeas ins bug: wrong number of arguments (1 for 3) Fixing #944 - changing error message from warning to info - connection recycled diff --git a/lib/puppet/provider/package/up2date.rb b/lib/puppet/provider/package/up2date.rb index dde633db4..8a1c22892 100644 --- a/lib/puppet/provider/package/up2date.rb +++ b/lib/puppet/provider/package/up2date.rb @@ -3,9 +3,11 @@ Puppet.type(:package).provide :up2date, :parent => :rpm, :source => :rpm do mechanism." commands :up2date => "/usr/sbin/up2date-nox" - defaultfor :operatingsystem => :redhat, + + defaultfor :operatingsystem => [:redhat, :oel, :ovm] :lsbdistrelease => ["2.1", "3", "4"] - confine :operatingsystem => :redhat + + confine :operatingsystem => [:redhat, :oel, :ovm] # Install a package using 'up2date'. def install diff --git a/lib/puppet/provider/service/redhat.rb b/lib/puppet/provider/service/redhat.rb index c6c3540f5..031db46c1 100755 --- a/lib/puppet/provider/service/redhat.rb +++ b/lib/puppet/provider/service/redhat.rb @@ -9,7 +9,7 @@ Puppet::Type.type(:service).provide :redhat, :parent => :init do commands :chkconfig => "/sbin/chkconfig", :service => "/sbin/service" - defaultfor :operatingsystem => [:redhat, :fedora, :suse, :centos, :sles] + defaultfor :operatingsystem => [:redhat, :fedora, :suse, :centos, :sles, :oel, :ovm] def self.defpath superclass.defpath -- cgit From 2a855510a3cca7d475c31495ccb502af606528dc Mon Sep 17 00:00:00 2001 From: Bryan Kearney Date: Fri, 13 Feb 2009 08:21:36 -0500 Subject: Bug 1948: Add logic and testing for the command parsing logic --- lib/puppet/provider/augeas/augeas.rb | 11 ++++++----- spec/unit/provider/augeas/augeas.rb | 17 +++++++++++++---- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/lib/puppet/provider/augeas/augeas.rb b/lib/puppet/provider/augeas/augeas.rb index fd47da389..8d4f6d55a 100644 --- a/lib/puppet/provider/augeas/augeas.rb +++ b/lib/puppet/provider/augeas/augeas.rb @@ -52,7 +52,6 @@ Puppet::Type.type(:augeas).provide(:augeas) do commands.concat(parse_commands(datum)) end end - return commands end @@ -179,18 +178,20 @@ Puppet::Type.type(:augeas).provide(:augeas) do debug("sending command '#{command}' with params #{cmd_array.inspect}") aug.clear(cmd_array[0]) when "insert", "ins" - if cmd_array.size < 3 + + ext_array = cmd_array[1].split(" ") ; + if cmd_array.size < 2 or ext_array.size < 2 fail("ins requires 3 parameters") end label = cmd_array[0] - where = cmd_array[1] - path = File.join(context, cmd_array[2]) + where = ext_array[0] + path = File.join(context, ext_array[1]) case where when "before": before = true when "after": before = false else fail("Invalid value '#{where}' for where param") end - debug("sending command '#{command}' with params #{[label, where, path]}") + debug("sending command '#{command}' with params #{[label, where, path].inspect()}") aug.insert(path, label, before) else fail("Command '#{command}' is not supported") end diff --git a/spec/unit/provider/augeas/augeas.rb b/spec/unit/provider/augeas/augeas.rb index 4bc1814e5..2def0d0c4 100644 --- a/spec/unit/provider/augeas/augeas.rb +++ b/spec/unit/provider/augeas/augeas.rb @@ -209,7 +209,7 @@ describe provider_class do it "should handle ins commands with before" do - command = [["ins", "Binks", "before", "/Jar/Jar"]] + command = [["ins", "Binks", "before /Jar/Jar"]] context = "/foo" @resource.expects(:[]).times(2).returns(command).then.returns(context) @augeas.expects(:insert).with("/foo/Jar/Jar", "Binks", true) @@ -218,16 +218,25 @@ describe provider_class do end it "should handle ins commands with before" do - command = [["ins", "Binks", "after", "/Jar/Jar"]] + command = [["ins", "Binks", "after /Jar/Jar"]] context = "/foo" @resource.expects(:[]).times(2).returns(command).then.returns(context) @augeas.expects(:insert).with("/foo/Jar/Jar", "Binks", false) @augeas.expects(:save).returns(true) @provider.execute_changes.should == :executed - end + end + + it "should handle ins with no context" do + command = [["ins", "Binks", "after /Jar/Jar"]] + context = "" # this is the default + @resource.expects(:[]).times(2).returns(command).then.returns(context) + @augeas.expects(:insert).with("/Jar/Jar", "Binks", false) + @augeas.expects(:save).returns(true) + @provider.execute_changes.should == :executed + end it "should handle multiple commands" do - command = [["ins", "Binks", "after", "/Jar/Jar"], ["clear", "/Jar/Jar"]] + command = [["ins", "Binks", "after /Jar/Jar"], ["clear", "/Jar/Jar"]] context = "/foo" @resource.expects(:[]).times(2).returns(command).then.returns(context) @augeas.expects(:insert).with("/foo/Jar/Jar", "Binks", false) -- cgit From 336b6458a2264c550adf8e6799162380822e0d97 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sat, 14 Feb 2009 02:10:08 +1100 Subject: Fixed #1830 - Added regsubst function --- CHANGELOG | 2 + lib/puppet/parser/functions/regsubst.rb | 93 +++++++++++++++++++++++++++++++++ spec/unit/parser/functions/regsubst.rb | 88 +++++++++++++++++++++++++++++++ 3 files changed, 183 insertions(+) create mode 100644 lib/puppet/parser/functions/regsubst.rb create mode 100644 spec/unit/parser/functions/regsubst.rb diff --git a/CHANGELOG b/CHANGELOG index a8e828793..cffb3ec7d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ 0.24.8 + Fixed #1830 - Added regsubst function + Updated up2date and service confines to add support for Oracle EL and VM Fixing #1948 and #1953 - augeas ins bug: wrong number of arguments (1 for 3) diff --git a/lib/puppet/parser/functions/regsubst.rb b/lib/puppet/parser/functions/regsubst.rb new file mode 100644 index 000000000..067d75c51 --- /dev/null +++ b/lib/puppet/parser/functions/regsubst.rb @@ -0,0 +1,93 @@ +module Puppet::Parser::Functions + newfunction(:regsubst, :type => :rvalue, + :doc => "\ + Perform regexp replacement on a string. + + Parameters (in order): + + :str: + The string to operate on. + + :regexp: + The regular expression matching the string. If you want it + anchored at the start and/or end of the string, you must do + that with ^ and $ yourself. + + :replacement: + Replacement string. Can contain back references to what was + matched using \\0, \\1, and so on. + + :flags: + Optional. String of single letter flags for how the regexp + is interpreted: + + - **E** + Extended regexps + - **I** + Ignore case in regexps + - **M** + Multiline regexps + - **G** + Global replacement; all occurances of the regexp in + the string will be replaced. Without this, only the + first occurance will be replaced. + + :lang: + Optional. How to handle multibyte characters. A + single-character string with the following values: + + - **N** + None + - **E** + EUC + - **S** + SJIS + - **U** + UTF-8 + + **Examples** + + Get the third octet from the node's IP address: :: + + $i3 = regsubst($ipaddress, + '^([0-9]+)[.]([0-9]+)[.]([0-9]+)[.]([0-9]+)$', + '\\\\3') + + Put angle brackets around each octet in the node's IP address: :: + + $x = regsubst($ipaddress, '([0-9]+)', '<\\\\1>', 'G') + ") \ + do |args| + flag_mapping = { + "E" => Regexp::EXTENDED, + "I" => Regexp::IGNORECASE, + "M" => Regexp::MULTILINE, + } + if args.length < 3 or args.length > 5 + raise Puppet::ParseError, ("regsub(): wrong number of arguments" + + " (#{args.length}; min 3, max 5)") + end + str, regexp, replacement, flags, lang = args + reflags = 0 + global = false + (flags or "").each_byte do |f| + f = f.chr + if f == "G" + global = true + else + fvalue = flag_mapping[f] + if !fvalue + raise Puppet::ParseError, "regsub(): bad flag `#{f}'" + end + reflags |= fvalue + end + end + re = Regexp.compile(regexp, reflags, lang) + if global + result = str.gsub(re, replacement) + else + result = str.sub(re, replacement) + end + return result + end +end diff --git a/spec/unit/parser/functions/regsubst.rb b/spec/unit/parser/functions/regsubst.rb new file mode 100644 index 000000000..18f49f7d4 --- /dev/null +++ b/spec/unit/parser/functions/regsubst.rb @@ -0,0 +1,88 @@ +#! /usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +describe "the regsubst function" do + + before :each do + @scope = Puppet::Parser::Scope.new() + end + + it "should exist" do + Puppet::Parser::Functions.function("regsubst").should == "function_regsubst" + end + + it "should raise a ParseError if there is less than 3 arguments" do + lambda { @scope.function_regsubst(["foo", "bar"]) }.should( + raise_error(Puppet::ParseError)) + end + + it "should raise a ParseError if there is more than 5 arguments" do + lambda { @scope.function_regsubst(["foo", "bar", "gazonk", "del", "x", "y"]) }.should( + raise_error(Puppet::ParseError)) + end + + + it "should raise a ParseError when given a bad flag" do + lambda { @scope.function_regsubst(["foo", "bar", "gazonk", "X"]) }.should( + raise_error(Puppet::ParseError)) + end + + it "should handle groups" do + result = @scope.function_regsubst( + [ '130.236.254.10', + '^([0-9]+)[.]([0-9]+)[.]([0-9]+)[.]([0-9]+)$', + '\4-\3-\2-\1' + ]) + result.should(eql("10-254-236-130")) + end + + it "should handle simple regexps" do + result = @scope.function_regsubst( + [ "the monkey breaks banana trees", + "b[an]*a", + "coconut" + ]) + result.should(eql("the monkey breaks coconut trees")) + end + + it "should handle case-sensitive regexps" do + result = @scope.function_regsubst( + [ "the monkey breaks baNAna trees", + "b[an]+a", + "coconut" + ]) + result.should(eql("the monkey breaks baNAna trees")) + end + + it "should handle case-insensitive regexps" do + result = @scope.function_regsubst( + [ "the monkey breaks baNAna trees", + "b[an]+a", + "coconut", + "I" + ]) + result.should(eql("the monkey breaks coconut trees")) + end + + it "should handle global substitutions" do + result = @scope.function_regsubst( + [ "the monkey breaks\tbanana trees", + "[ \t]", + "--", + "G" + ]) + result.should(eql("the--monkey--breaks--banana--trees")) + end + + it "should handle global substitutions with groups" do + result = @scope.function_regsubst( + [ '130.236.254.10', + '([0-9]+)', + '<\1>', + 'G' + ]) + result.should(eql('<130>.<236>.<254>.<10>')) + end + +end -- cgit From 1bc7404f5e3efa633bb0a5cf10828071cfc4d876 Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sat, 14 Feb 2009 02:11:21 +1100 Subject: Fixed #1831 - Added sprintf function --- CHANGELOG | 2 ++ lib/puppet/parser/functions/sprintf.rb | 17 ++++++++++++++ spec/unit/parser/functions/sprintf.rb | 42 ++++++++++++++++++++++++++++++++++ 3 files changed, 61 insertions(+) create mode 100644 lib/puppet/parser/functions/sprintf.rb create mode 100644 spec/unit/parser/functions/sprintf.rb diff --git a/CHANGELOG b/CHANGELOG index cffb3ec7d..f84c69a06 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,6 @@ 0.24.8 + Fixed #1831 - Added sprintf function + Fixed #1830 - Added regsubst function Updated up2date and service confines to add support for Oracle EL and VM diff --git a/lib/puppet/parser/functions/sprintf.rb b/lib/puppet/parser/functions/sprintf.rb new file mode 100644 index 000000000..79744104d --- /dev/null +++ b/lib/puppet/parser/functions/sprintf.rb @@ -0,0 +1,17 @@ +module Puppet::Parser::Functions + newfunction(:sprintf, :type => :rvalue, + :doc => "\ + Perform printf-style formatting of text. + + The first parameter is format string describing how the rest of the + parameters should be formatted. See the documentation for the + ``Kernel::sprintf()`` function in Ruby for all the details. + ") \ + do |args| + if args.length < 1 + raise Puppet::ParseError, 'sprintf() needs at least one argument' + end + fmt = args.shift() + return sprintf(fmt, *args) + end +end diff --git a/spec/unit/parser/functions/sprintf.rb b/spec/unit/parser/functions/sprintf.rb new file mode 100644 index 000000000..8654b18fc --- /dev/null +++ b/spec/unit/parser/functions/sprintf.rb @@ -0,0 +1,42 @@ +#! /usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +describe "the sprintf function" do + + before :each do + @scope = Puppet::Parser::Scope.new() + end + + it "should exist" do + Puppet::Parser::Functions.function("sprintf").should == "function_sprintf" + end + + it "should raise a ParseError if there is less than 1 argument" do + lambda { @scope.function_sprintf([]) }.should( + raise_error(Puppet::ParseError)) + end + + it "should format integers" do + result = @scope.function_sprintf(["%+05d", "23"]) + result.should(eql("+0023")) + end + + it "should format floats" do + result = @scope.function_sprintf(["%+.2f", "2.7182818284590451"]) + result.should(eql("+2.72")) + end + + it "should format large floats" do + result = @scope.function_sprintf(["%+.2e", "27182818284590451"]) + result.should(eql("+2.72e+16")) + end + + it "should perform more complex formatting" do + result = @scope.function_sprintf( + [ "<%.8s:%#5o %#8X (%-8s)>", + "overlongstring", "23", "48879", "foo" ]) + result.should(eql("")) + end + +end -- cgit From 7d7218698ffbe917f4c0882d47bee32a938f79ce Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sat, 14 Feb 2009 03:06:39 +1100 Subject: Removed site from Puppet VIM syntax --- ext/vim/syntax/puppet.vim | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/vim/syntax/puppet.vim b/ext/vim/syntax/puppet.vim index 5a50f5ce0..e54d32aa8 100644 --- a/ext/vim/syntax/puppet.vim +++ b/ext/vim/syntax/puppet.vim @@ -16,7 +16,7 @@ elseif exists("b:current_syntax") finish endif -syn region puppetDefine start="^\s*\(class\|define\|site\|node\)" end="{" contains=puppetDefType,puppetDefName,puppetDefArguments +syn region puppetDefine start="^\s*\(class\|define\|node\)" end="{" contains=puppetDefType,puppetDefName,puppetDefArguments syn keyword puppetDefType class define site node inherits contained syn keyword puppetInherits inherits contained syn region puppetDefArguments start="(" end=")" contains=puppetArgument -- cgit From 2561c8e252dcf66890513458750bb1329a03beec Mon Sep 17 00:00:00 2001 From: James Turnbull Date: Sat, 14 Feb 2009 09:57:49 +1100 Subject: Updated Augeas type code --- lib/puppet/type/augeas.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/puppet/type/augeas.rb b/lib/puppet/type/augeas.rb index 67b62e886..c89400b5e 100644 --- a/lib/puppet/type/augeas.rb +++ b/lib/puppet/type/augeas.rb @@ -92,10 +92,12 @@ Puppet::Type.newtype(:augeas) do rm [PATH] Removes the node at location PATH remove [PATH] Synonym for rm clear [PATH] Keeps the node at PATH, but removes the value. - ins [PATH] Inserts an empty node at PATH. - insert [PATH] Synonym for ins + ins [LABEL] [WHERE] [PATH] + Inserts an empty node LABEL either [WHERE={before|after}] PATH. + insert [LABEL] [WHERE] [PATH] + Synonym for ins - If the parameter 'context' is set that that value is prepended to PATH" + If the parameter 'context' is set that value is prepended to PATH" munge do |value| provider.parse_commands(value) -- cgit