diff options
58 files changed, 2187 insertions, 602 deletions
@@ -1,5 +1,6 @@ -2.6.7rc1 -======== +2.6.7 +===== +17f673d Updated CHANGELOG for 2.6.7rc1 852fb97 (#5073) Download plugins even if you're filtering on tags 4f34dbf Fix #5610: Prevent unnecessary RAL lookups 9781032 Revert "Merge branch 'ticket/2.6.x/5605' of git://github.com/stschulte/puppet into 2.6.next" diff --git a/install.rb b/install.rb index e8755e07a..351ba25d2 100755 --- a/install.rb +++ b/install.rb @@ -92,8 +92,12 @@ def do_configs(configs, target, strip = 'conf/') Dir.mkdir(target) unless File.directory? target configs.each do |cf| ocf = File.join(InstallOptions.config_dir, cf.gsub(/#{strip}/, '')) - File.install(cf, ocf, 0644, true) - end + if $haveftools + File.install(cf, ocf, 0644, true) + else + FileUtils.install(cf, ocf, {:mode => 0644, :verbose => true}) + end + end end def do_bins(bins, target, strip = 's?bin/') diff --git a/lib/puppet/application/agent.rb b/lib/puppet/application/agent.rb index 3749241f8..9f5921de1 100644 --- a/lib/puppet/application/agent.rb +++ b/lib/puppet/application/agent.rb @@ -229,6 +229,8 @@ class Puppet::Application::Agent < Puppet::Application Puppet::SSL::Host.ca_location = options[:fingerprint] ? :none : :remote Puppet::Transaction::Report.terminus_class = :rest + # we want the last report to be persisted locally + Puppet::Transaction::Report.cache_class = :yaml # Override the default; puppetd needs this, usually. # You can still override this on the command-line with, e.g., :compiler. diff --git a/lib/puppet/application/apply.rb b/lib/puppet/application/apply.rb index cc733e1f5..7f0b95a89 100644 --- a/lib/puppet/application/apply.rb +++ b/lib/puppet/application/apply.rb @@ -148,6 +148,9 @@ class Puppet::Application::Apply < Puppet::Application exit(1) end + # we want the last report to be persisted locally + Puppet::Transaction::Report.cache_class = :yaml + if options[:debug] Puppet::Util::Log.level = :debug elsif options[:verbose] diff --git a/lib/puppet/application/master.rb b/lib/puppet/application/master.rb index 6d1cdef1b..c5e9ada54 100644 --- a/lib/puppet/application/master.rb +++ b/lib/puppet/application/master.rb @@ -136,7 +136,7 @@ class Puppet::Application::Master < Puppet::Application exit(Puppet.settings.print_configs ? 0 : 1) if Puppet.settings.print_configs? - Puppet.settings.use :main, :master, :ssl + Puppet.settings.use :main, :master, :ssl, :metrics # Cache our nodes in yaml. Currently not configurable. Puppet::Node.cache_class = :yaml diff --git a/lib/puppet/configurer.rb b/lib/puppet/configurer.rb index d3c902576..9f68ca499 100644 --- a/lib/puppet/configurer.rb +++ b/lib/puppet/configurer.rb @@ -166,19 +166,28 @@ class Puppet::Configurer execute_postrun_command Puppet::Util::Log.close(report) - send_report(report, transaction) end def send_report(report, trans) report.finalize_report if trans puts report.summary if Puppet[:summarize] + save_last_run_summary(report) report.save if Puppet[:report] rescue => detail puts detail.backtrace if Puppet[:trace] Puppet.err "Could not send report: #{detail}" end + def save_last_run_summary(report) + Puppet::Util::FileLocking.writelock(Puppet[:lastrunfile], 0660) do |file| + file.print YAML.dump(report.raw_summary) + end + rescue => detail + puts detail.backtrace if Puppet[:trace] + Puppet.err "Could not save last run local report: #{detail}" + end + private def self.timeout diff --git a/lib/puppet/defaults.rb b/lib/puppet/defaults.rb index 8da104086..2a1ded592 100644 --- a/lib/puppet/defaults.rb +++ b/lib/puppet/defaults.rb @@ -474,6 +474,7 @@ module Puppet setdefaults(:metrics, :rrddir => {:default => "$vardir/rrd", + :mode => 0750, :owner => "service", :group => "service", :desc => "The directory where RRD database files are stored. @@ -606,6 +607,14 @@ module Puppet :report => [false, "Whether to send reports after every transaction." ], + :lastrunfile => { :default => "$statedir/last_run_summary.yaml", + :mode => 0660, + :desc => "Where puppet agent stores the last run report summary in yaml format." + }, + :lastrunreport => { :default => "$statedir/last_run_report.yaml", + :mode => 0660, + :desc => "Where puppet agent stores the last run report in yaml format." + }, :graph => [false, "Whether to create dot graph files for the different configuration graphs. These dot files can be interpreted by tools like OmniGraffle or dot (which is part of ImageMagick)."], diff --git a/lib/puppet/file_serving/fileset.rb b/lib/puppet/file_serving/fileset.rb index fdbcf93a3..c020f036d 100644 --- a/lib/puppet/file_serving/fileset.rb +++ b/lib/puppet/file_serving/fileset.rb @@ -59,6 +59,7 @@ class Puppet::FileServing::Fileset end def initialize(path, options = {}) + path = path.chomp(File::SEPARATOR) raise ArgumentError.new("Fileset paths must be fully qualified") unless File.expand_path(path) == path @path = path diff --git a/lib/puppet/indirector/exec.rb b/lib/puppet/indirector/exec.rb index fa789442b..4683eda0f 100644 --- a/lib/puppet/indirector/exec.rb +++ b/lib/puppet/indirector/exec.rb @@ -35,8 +35,7 @@ class Puppet::Indirector::Exec < Puppet::Indirector::Terminus begin output = execute(external_command) rescue Puppet::ExecutionFailure => detail - Puppet.err "Failed to find #{name} via exec: #{detail}" - return nil + raise Puppet::Error, "Failed to find #{name} via exec: #{detail}" end if output =~ /\A\s*\Z/ # all whitespace diff --git a/lib/puppet/indirector/report/yaml.rb b/lib/puppet/indirector/report/yaml.rb new file mode 100644 index 000000000..bf7bf4fe5 --- /dev/null +++ b/lib/puppet/indirector/report/yaml.rb @@ -0,0 +1,11 @@ +require 'puppet/transaction/report' +require 'puppet/indirector/yaml' + +class Puppet::Transaction::Report::Yaml < Puppet::Indirector::Yaml + desc "Store last report as a flat file, serialized using YAML." + + # Force report to be saved there + def path(name,ext='.yaml') + Puppet[:lastrunreport] + end +end diff --git a/lib/puppet/node/environment.rb b/lib/puppet/node/environment.rb index b64fb8a1f..807479bc8 100644 --- a/lib/puppet/node/environment.rb +++ b/lib/puppet/node/environment.rb @@ -79,7 +79,7 @@ class Puppet::Node::Environment # environment has changed do we delve deeper. Thread.current[:known_resource_types] = nil if (krt = Thread.current[:known_resource_types]) && krt.environment != self Thread.current[:known_resource_types] ||= synchronize { - if @known_resource_types.nil? or @known_resource_types.stale? + if @known_resource_types.nil? or @known_resource_types.require_reparse? @known_resource_types = Puppet::Resource::TypeCollection.new(self) @known_resource_types.perform_initial_import end diff --git a/lib/puppet/parameter.rb b/lib/puppet/parameter.rb index ff7cab22b..29d60fc66 100644 --- a/lib/puppet/parameter.rb +++ b/lib/puppet/parameter.rb @@ -300,3 +300,5 @@ class Puppet::Parameter name.to_s end end + +require 'puppet/parameter/path' diff --git a/lib/puppet/parameter/path.rb b/lib/puppet/parameter/path.rb new file mode 100644 index 000000000..44886afd0 --- /dev/null +++ b/lib/puppet/parameter/path.rb @@ -0,0 +1,42 @@ +require 'puppet/parameter' + +class Puppet::Parameter::Path < Puppet::Parameter + def self.accept_arrays(bool = true) + @accept_arrays = !!bool + end + def self.arrays? + @accept_arrays + end + + def validate_path(paths) + if paths.is_a?(Array) and ! self.class.arrays? then + fail "#{name} only accepts a single path, not an array of paths" + end + + # We *always* support Unix path separators, as Win32 does now too. + absolute = "[/#{::Regexp.quote(::File::SEPARATOR)}]" + win32 = Puppet.features.microsoft_windows? + + Array(paths).each do |path| + next if path =~ %r{^#{absolute}} + next if win32 and path =~ %r{^(?:[a-zA-Z]:)?#{absolute}} + fail("#{name} must be a fully qualified path") + end + + paths + end + + # This will be overridden if someone uses the validate option, which is why + # it just delegates to the other, useful, method. + def unsafe_validate(paths) + validate_path(paths) + end + + # Likewise, this might be overridden, but by default... + def unsafe_munge(paths) + if paths.is_a?(Array) and ! self.class.arrays? then + fail "#{name} only accepts a single path, not an array of paths" + end + paths + end +end diff --git a/lib/puppet/parser/compiler.rb b/lib/puppet/parser/compiler.rb index fdabd05c9..6e8e3d26b 100644 --- a/lib/puppet/parser/compiler.rb +++ b/lib/puppet/parser/compiler.rb @@ -162,7 +162,7 @@ class Puppet::Parser::Compiler resource.evaluate unless lazy_evaluate found << name else - Puppet.info "Could not find class #{name} for #{node.name}" + Puppet.warning "Could not find class #{name} for #{node.name}" @catalog.tag(name) end end diff --git a/lib/puppet/parser/lexer.rb b/lib/puppet/parser/lexer.rb index 9a25263f6..71d9440ff 100644 --- a/lib/puppet/parser/lexer.rb +++ b/lib/puppet/parser/lexer.rb @@ -312,7 +312,8 @@ class Puppet::Parser::Lexer def file=(file) @file = file @line = 1 - @scanner = StringScanner.new(File.read(file)) + contents = File.exists?(file) ? File.read(file) : "" + @scanner = StringScanner.new(contents) end def shift_token @@ -547,7 +548,7 @@ class Puppet::Parser::Lexer value,terminator = slurpstring('"$') token_queue << [TOKENS[token_type[terminator]],preamble+value] if terminator != '$' or @scanner.scan(/\{/) - token_queue.shift + token_queue.shift elsif var_name = @scanner.scan(%r{(\w*::)*\w+|[0-9]}) token_queue << [TOKENS[:VARIABLE],var_name] tokenize_interpolated_string(DQ_continuation_token_types) diff --git a/lib/puppet/parser/parser_support.rb b/lib/puppet/parser/parser_support.rb index 7a0aa2601..9e580efb2 100644 --- a/lib/puppet/parser/parser_support.rb +++ b/lib/puppet/parser/parser_support.rb @@ -88,7 +88,6 @@ class Puppet::Parser::Parser unless file =~ /\.pp$/ file = file + ".pp" end - raise Puppet::Error, "Could not find file #{file}" unless FileTest.exist?(file) end raise Puppet::AlreadyImportedError, "Import loop detected" if known_resource_types.watching_file?(file) diff --git a/lib/puppet/provider/exec/posix.rb b/lib/puppet/provider/exec/posix.rb new file mode 100644 index 000000000..92dbd8c98 --- /dev/null +++ b/lib/puppet/provider/exec/posix.rb @@ -0,0 +1,112 @@ +Puppet::Type.type(:exec).provide :posix do + include Puppet::Util::Execution + + confine :feature => :posix + defaultfor :feature => :posix + + desc "Execute external binaries directly, on POSIX systems. +This does not pass through a shell, or perform any interpolation, but +only directly calls the command with the arguments given." + + def run(command, check = false) + output = nil + status = nil + dir = nil + + checkexe(command) + + if dir = resource[:cwd] + unless File.directory?(dir) + if check + dir = nil + else + self.fail "Working directory '#{dir}' does not exist" + end + end + end + + dir ||= Dir.pwd + + debug "Executing#{check ? " check": ""} '#{command}'" + begin + # Do our chdir + Dir.chdir(dir) do + environment = {} + + environment[:PATH] = resource[:path].join(":") if resource[:path] + + if envlist = resource[:environment] + envlist = [envlist] unless envlist.is_a? Array + envlist.each do |setting| + if setting =~ /^(\w+)=((.|\n)+)$/ + env_name = $1 + value = $2 + if environment.include?(env_name) || environment.include?(env_name.to_sym) + warning "Overriding environment setting '#{env_name}' with '#{value}'" + end + environment[env_name] = value + else + warning "Cannot understand environment setting #{setting.inspect}" + end + end + end + + withenv environment do + Timeout::timeout(resource[:timeout]) do + output, status = Puppet::Util::SUIDManager. + run_and_capture([command], resource[:user], resource[:group]) + end + # The shell returns 127 if the command is missing. + if status.exitstatus == 127 + raise ArgumentError, output + end + end + end + rescue Errno::ENOENT => detail + self.fail detail.to_s + end + + return output, status + end + + # Verify that we have the executable + def checkexe(command) + exe = extractexe(command) + + if resource[:path] + if Puppet.features.posix? and !File.exists?(exe) + withenv :PATH => resource[:path].join(File::PATH_SEPARATOR) do + exe = which(exe) || raise(ArgumentError,"Could not find command '#{exe}'") + end + elsif Puppet.features.microsoft_windows? and !File.exists?(exe) + resource[:path].each do |path| + [".exe", ".ps1", ".bat", ".com", ""].each do |extension| + file = File.join(path, exe+extension) + return if File.exists?(file) + end + end + end + end + + raise ArgumentError, "Could not find command '#{exe}'" unless File.exists?(exe) + unless File.executable?(exe) + raise ArgumentError, + "'#{exe}' is not executable" + end + end + + def extractexe(command) + # easy case: command was quoted + if command =~ /^"([^"]+)"/ + $1 + else + command.split(/ /)[0] + end + end + + def validatecmd(command) + exe = extractexe(command) + # if we're not fully qualified, require a path + self.fail "'#{command}' is not qualified and no path was specified. Please qualify the command or specify a path." if File.expand_path(exe) != exe and resource[:path].nil? + end +end diff --git a/lib/puppet/provider/exec/shell.rb b/lib/puppet/provider/exec/shell.rb new file mode 100644 index 000000000..98f309e8f --- /dev/null +++ b/lib/puppet/provider/exec/shell.rb @@ -0,0 +1,17 @@ +Puppet::Type.type(:exec).provide :shell, :parent => :posix do + include Puppet::Util::Execution + + confine :feature => :posix + + desc "Execute external binaries directly, on POSIX systems. +passing through a shell so that shell built ins are available." + + def run(command, check = false) + command = %Q{/bin/sh -c "#{command.gsub(/"/,'\"')}"} + super(command, check) + end + + def validatecmd(command) + true + end +end diff --git a/lib/puppet/provider/package/gem.rb b/lib/puppet/provider/package/gem.rb index 19414cec4..28731c849 100755 --- a/lib/puppet/provider/package/gem.rb +++ b/lib/puppet/provider/package/gem.rb @@ -22,7 +22,7 @@ Puppet::Type.type(:package).provide :gem, :parent => Puppet::Provider::Package d end if name = hash[:justme] - command << name + command << name + "$" end begin @@ -94,7 +94,7 @@ Puppet::Type.type(:package).provide :gem, :parent => Puppet::Provider::Package d command << "--source" << "#{source}" << resource[:name] end else - command << resource[:name] + command << "--no-rdoc" << "--no-ri" << resource[:name] end output = execute(command) diff --git a/lib/puppet/provider/service/debian.rb b/lib/puppet/provider/service/debian.rb index 3d09e2849..58b808a8e 100755 --- a/lib/puppet/provider/service/debian.rb +++ b/lib/puppet/provider/service/debian.rb @@ -22,8 +22,12 @@ Puppet::Type.type(:service).provide :debian, :parent => :init do # Remove the symlinks def disable - update_rc "-f", @resource[:name], "remove" - update_rc @resource[:name], "stop", "00", "1", "2", "3", "4", "5", "6", "." + if `dpkg --compare-versions $(dpkg-query -W --showformat '${Version}' sysv-rc) ge 2.88 ; echo $?`.to_i == 0 + update_rc @resource[:name], "disable" + else + update_rc "-f", @resource[:name], "remove" + update_rc @resource[:name], "stop", "00", "1", "2", "3", "4", "5", "6", "." + end end def enabled? diff --git a/lib/puppet/resource/type_collection.rb b/lib/puppet/resource/type_collection.rb index 277d37b18..4210475ad 100644 --- a/lib/puppet/resource/type_collection.rb +++ b/lib/puppet/resource/type_collection.rb @@ -162,17 +162,22 @@ class Puppet::Resource::TypeCollection parser.string = code else file = Puppet.settings.value(:manifest, environment.to_s) - return unless File.exist?(file) parser.file = file end parser.parse rescue => detail + @parse_failed = true + msg = "Could not parse for environment #{environment}: #{detail}" error = Puppet::Error.new(msg) error.set_backtrace(detail.backtrace) raise error end + def require_reparse? + @parse_failed || stale? + end + def stale? @watched_files.values.detect { |file| file.changed? } end diff --git a/lib/puppet/simple_graph.rb b/lib/puppet/simple_graph.rb index c5dac0f6c..c658b3b92 100644 --- a/lib/puppet/simple_graph.rb +++ b/lib/puppet/simple_graph.rb @@ -329,7 +329,7 @@ class Puppet::SimpleGraph children = other.adjacent(container, :direction => :out) # MQR TODO: Luke suggests that it should be possible to refactor the system so that - # container nodes are retained, thus obviating the need for the whit. + # container nodes are retained, thus obviating the need for the whit. children = [whit_class.new(:name => container.name, :catalog => other)] if children.empty? # First create new edges for each of the :in edges diff --git a/lib/puppet/transaction.rb b/lib/puppet/transaction.rb index aa650eea1..48154ad6f 100644 --- a/lib/puppet/transaction.rb +++ b/lib/puppet/transaction.rb @@ -47,7 +47,7 @@ class Puppet::Transaction def apply(resource, ancestor = nil) status = resource_harness.evaluate(resource) add_resource_status(status) - event_manager.queue_events(ancestor || resource, status.events) + event_manager.queue_events(ancestor || resource, status.events) unless status.failed? rescue => detail resource.err "Could not evaluate: #{detail}" end diff --git a/lib/puppet/transaction/report.rb b/lib/puppet/transaction/report.rb index 8e04759ad..16fee42ae 100644 --- a/lib/puppet/transaction/report.rb +++ b/lib/puppet/transaction/report.rb @@ -80,30 +80,49 @@ class Puppet::Transaction::Report host end - # Provide a summary of this report. + # Provide a human readable textual summary of this report. def summary + report = raw_summary + ret = "" + report.keys.sort { |a,b| a.to_s <=> b.to_s }.each do |key| + ret += "#{Puppet::Util::Metric.labelize(key)}:\n" - @metrics.sort { |a,b| a[1].label <=> b[1].label }.each do |name, metric| - ret += "#{metric.label}:\n" - metric.values.sort { |a,b| + report[key].keys.sort { |a,b| # sort by label - if a[0] == :total + if a == :total 1 - elsif b[0] == :total + elsif b == :total -1 else - a[1] <=> b[1] + report[key][a].to_s <=> report[key][b].to_s end - }.each do |name, label, value| + }.each do |label| + value = report[key][label] next if value == 0 value = "%0.2f" % value if value.is_a?(Float) - ret += " %15s %s\n" % [label + ":", value] + ret += " %15s %s\n" % [Puppet::Util::Metric.labelize(label) + ":", value] end end ret end + # Provide a raw hash summary of this report. + def raw_summary + report = {} + + @metrics.each do |name, metric| + key = metric.name.to_s + report[key] = {} + metric.values.each do |name, label, value| + report[key][name.to_s] = value + end + report[key]["total"] = 0 unless key == "time" or report[key].include?("total") + end + (report["time"] ||= {})["last_run"] = Time.now.tv_sec + report + end + # Based on the contents of this report's metrics, compute a single number # that represents the report. The resulting number is a bitmask where # individual bits represent the presence of different metrics. @@ -142,7 +161,6 @@ class Puppet::Transaction::Report metrics["total"] = resource_statuses.length resource_statuses.each do |name, status| - Puppet::Resource::Status::STATES.each do |state| metrics[state.to_s] += 1 if status.send(state) end diff --git a/lib/puppet/type/exec.rb b/lib/puppet/type/exec.rb index 5ed2b104c..be0ece023 100755 --- a/lib/puppet/type/exec.rb +++ b/lib/puppet/type/exec.rb @@ -23,17 +23,15 @@ module Puppet you are doing a lot of work with `exec`, please at least notify us at Puppet Labs what you are doing, and hopefully we can work with you to get a native resource type for the work you are doing. - - **Autorequires:** If Puppet is managing an exec's cwd or the executable file used in an exec's command, the exec resource will autorequire those files. If Puppet is managing the user that an exec should run as, the exec resource will autorequire that user." - require 'open3' + **Autorequires:** If Puppet is managing an exec's cwd or the executable file used in an exec's command, the exec resource will autorequire those files. If Puppet is managing the user that an exec should run as, the exec resource will autorequire that user." # Create a new check mechanism. It's basically just a parameter that # provides one extra 'check' method. - def self.newcheck(name, &block) + def self.newcheck(name, options = {}, &block) @checks ||= {} - check = newparam(name, &block) + check = newparam(name, options, &block) @checks[name] = check end @@ -65,9 +63,11 @@ module Puppet # First verify that all of our checks pass. def retrieve - # Default to somethinng - - if @resource.check + # We need to return :notrun to trigger evaluation; when that isn't + # true, we *LIE* about what happened and return a "success" for the + # value, which causes us to be treated as in_sync?, which means we + # don't actually execute anything. I think. --daniel 2011-03-10 + if @resource.check_all_attributes return :notrun else return self.should @@ -89,7 +89,7 @@ module Puppet tries.times do |try| # Only add debug messages for tries > 1 to reduce log spam. debug("Exec try #{try+1}/#{tries}") if tries > 1 - @output, @status = @resource.run(self.resource[:command]) + @output, @status = provider.run(self.resource[:command]) break if self.should.include?(@status.exitstatus.to_s) if try_sleep > 0 and tries > 1 debug("Sleeping for #{try_sleep} seconds between tries") @@ -139,7 +139,7 @@ module Puppet newparam(:path) do desc "The search path used for command execution. Commands must be fully qualified if no path is specified. Paths - can be specified as an array or as a colon-separated list." + can be specified as an array or as a colon separated list." # Support both arrays and colon-separated fields. def value=(*values) @@ -176,21 +176,9 @@ module Puppet # Validation is handled by the SUIDManager class. end - newparam(:cwd) do + newparam(:cwd, :parent => Puppet::Parameter::Path) do desc "The directory from which to run the command. If this directory does not exist, the command will fail." - - validate do |dir| - unless dir =~ /^#{File::SEPARATOR}/ - self.fail("CWD must be a fully qualified path") - end - end - - munge do |dir| - dir = dir[0] if dir.is_a?(Array) - - dir - end end newparam(:logoutput) do @@ -209,7 +197,7 @@ module Puppet for refreshing." validate do |command| - @resource.validatecmd(command) + provider.validatecmd(command) end end @@ -241,19 +229,17 @@ module Puppet newparam(:timeout) do desc "The maximum time the command should take. If the command takes longer than the timeout, the command is considered to have failed - and will be stopped. Use any negative number to disable the timeout. + and will be stopped. Use 0 to disable the timeout. The time is specified in seconds." munge do |value| value = value.shift if value.is_a?(Array) - if value.is_a?(String) - unless value =~ /^[-\d.]+$/ - raise ArgumentError, "The timeout must be a number." - end - Float(value) - else - value + begin + value = Float(value) + rescue ArgumentError => e + raise ArgumentError, "The timeout must be a number." end + [value, 0.0].max end defaultto 300 @@ -333,7 +319,7 @@ module Puppet end end - newcheck(:creates) do + newcheck(:creates, :parent => Puppet::Parameter::Path) do desc "A file that this command creates. If this parameter is provided, then the command will only be run if the specified file does not exist: @@ -346,19 +332,7 @@ module Puppet " - # FIXME if they try to set this and fail, then we should probably - # fail the entire exec, right? - validate do |files| - files = [files] unless files.is_a? Array - - files.each do |file| - self.fail("'creates' must be set to a fully qualified path") unless file - - unless file =~ %r{^#{File::SEPARATOR}} - self.fail "'creates' files must be fully qualified." - end - end - end + accept_arrays # If the file exists, return false (i.e., don't run the command), # else return true @@ -386,15 +360,15 @@ module Puppet validate do |cmds| cmds = [cmds] unless cmds.is_a? Array - cmds.each do |cmd| - @resource.validatecmd(cmd) + cmds.each do |command| + provider.validatecmd(command) end end # Return true if the command does not return 0. def check(value) begin - output, status = @resource.run(value, true) + output, status = provider.run(value, true) rescue Timeout::Error err "Check #{value.inspect} exceeded timeout" return false @@ -428,15 +402,15 @@ module Puppet validate do |cmds| cmds = [cmds] unless cmds.is_a? Array - cmds.each do |cmd| - @resource.validatecmd(cmd) + cmds.each do |command| + provider.validatecmd(command) end end # Return true if the command returns 0. def check(value) begin - output, status = @resource.run(value, true) + output, status = provider.run(value, true) rescue Timeout::Error err "Check #{value.inspect} exceeded timeout" return false @@ -450,7 +424,7 @@ module Puppet @isomorphic = false validate do - validatecmd(self[:command]) + provider.validatecmd(self[:command]) end # FIXME exec should autorequire any exec that 'creates' our cwd @@ -503,7 +477,7 @@ module Puppet # Verify that we pass all of the checks. The argument determines whether # we skip the :refreshonly check, which is necessary because we now check # within refresh - def check(refreshing = false) + def check_all_attributes(refreshing = false) self.class.checks.each { |check| next if refreshing and check == :refreshonly if @parameters.include?(check) @@ -518,32 +492,6 @@ module Puppet true end - # Verify that we have the executable - def checkexe(cmd) - exe = extractexe(cmd) - - if self[:path] - if Puppet.features.posix? and !File.exists?(exe) - withenv :PATH => self[:path].join(File::PATH_SEPARATOR) do - exe = which(exe) || raise(ArgumentError,"Could not find command '#{exe}'") - end - elsif Puppet.features.microsoft_windows? and !File.exists?(exe) - self[:path].each do |path| - [".exe", ".ps1", ".bat", ".com", ""].each do |extension| - file = File.join(path, exe+extension) - return if File.exists?(file) - end - end - end - end - - raise ArgumentError, "Could not find executable '#{exe}'" unless FileTest.exists?(exe) - unless FileTest.executable?(exe) - raise ArgumentError, - "'#{exe}' is not executable" - end - end - def output if self.property(:returns).nil? return nil @@ -554,98 +502,13 @@ module Puppet # Run the command, or optionally run a separately-specified command. def refresh - if self.check(true) + if self.check_all_attributes(true) if cmd = self[:refresh] - self.run(cmd) + provider.run(cmd) else self.property(:returns).sync end end end - - # Run a command. - def run(command, check = false) - output = nil - status = nil - - dir = nil - - checkexe(command) - - if dir = self[:cwd] - unless File.directory?(dir) - if check - dir = nil - else - self.fail "Working directory '#{dir}' does not exist" - end - end - end - - dir ||= Dir.pwd - - if check - debug "Executing check '#{command}'" - else - debug "Executing '#{command}'" - end - begin - # Do our chdir - Dir.chdir(dir) do - environment = {} - - environment[:PATH] = self[:path].join(":") if self[:path] - - if envlist = self[:environment] - envlist = [envlist] unless envlist.is_a? Array - envlist.each do |setting| - if setting =~ /^(\w+)=((.|\n)+)$/ - name = $1 - value = $2 - if environment.include? name - warning( - "Overriding environment setting '#{name}' with '#{value}'" - ) - end - environment[name] = value - else - warning "Cannot understand environment setting #{setting.inspect}" - end - end - end - - withenv environment do - Timeout::timeout(self[:timeout]) do - output, status = Puppet::Util::SUIDManager.run_and_capture( - [command], self[:user], self[:group] - ) - end - # The shell returns 127 if the command is missing. - if status.exitstatus == 127 - raise ArgumentError, output - end - end - end - rescue Errno::ENOENT => detail - self.fail detail.to_s - end - - return output, status - end - - def validatecmd(cmd) - exe = extractexe(cmd) - # if we're not fully qualified, require a path - self.fail "'#{cmd}' is not qualified and no path was specified. Please qualify the command or specify a path." if File.expand_path(exe) != exe and self[:path].nil? - end - - def extractexe(cmd) - # easy case: command was quoted - if cmd =~ /^"([^"]+)"/ - $1 - else - cmd.split(/ /)[0] - end - end end end diff --git a/lib/puppet/type/file.rb b/lib/puppet/type/file.rb index e1a4ecbb9..630ebe5de 100644 --- a/lib/puppet/type/file.rb +++ b/lib/puppet/type/file.rb @@ -122,7 +122,18 @@ Puppet::Type.newtype(:file) do newparam(:recurse) do desc "Whether and how deeply to do recursive - management." + management. Options are: + + * `inf,true` --- Regular style recursion on both remote and local + directory structure. + * `remote` --- Descends recursively into the remote directory + but not the local directory. Allows copying of + a few files into a directory containing many + unmanaged files without scanning all the local files. + * `false` --- Default of no recursion. + * `[0-9]+` --- Same as true, but limit recursion. Warning: this syntax + has been deprecated in favor of the `recurselimit` attribute. + " newvalues(:true, :false, :inf, :remote, /^[0-9]+$/) diff --git a/lib/puppet/util/command_line/puppetrun b/lib/puppet/util/command_line/puppetrun index 7eba3b2c4..3437405b0 100755 --- a/lib/puppet/util/command_line/puppetrun +++ b/lib/puppet/util/command_line/puppetrun @@ -107,7 +107,6 @@ # option requires LDAP support at this point. # # ping:: -# # Do a ICMP echo against the target host. Skip hosts that don't respond to ping. # # = Example diff --git a/lib/puppet/util/loadedfile.rb b/lib/puppet/util/loadedfile.rb index 735dba459..d2f5d0923 100755 --- a/lib/puppet/util/loadedfile.rb +++ b/lib/puppet/util/loadedfile.rb @@ -34,10 +34,6 @@ module Puppet # Create the file. Must be passed the file path. def initialize(file) @file = file - unless FileTest.exists?(@file) - raise Puppet::NoSuchFile, - "Can not use a non-existent file for parsing" - end @statted = 0 @stamp = nil @tstamp = stamp @@ -50,7 +46,7 @@ module Puppet @statted = Time.now.to_i begin @stamp = File.stat(@file).ctime - rescue Errno::ENOENT + rescue Errno::ENOENT, Errno::ENOTDIR @stamp = Time.now end end diff --git a/lib/puppet/util/metric.rb b/lib/puppet/util/metric.rb index 09bbb6137..835e1d610 100644 --- a/lib/puppet/util/metric.rb +++ b/lib/puppet/util/metric.rb @@ -122,7 +122,7 @@ class Puppet::Util::Metric def initialize(name,label = nil) @name = name.to_s - @label = label || labelize(name) + @label = label || self.class.labelize(name) @values = [] end @@ -133,7 +133,7 @@ class Puppet::Util::Metric def newvalue(name,value,label = nil) raise ArgumentError.new("metric name #{name.inspect} is not a string") unless name.is_a? String - label ||= labelize(name) + label ||= self.class.labelize(name) @values.push [name,label,value] end @@ -174,10 +174,8 @@ class Puppet::Util::Metric @values.sort { |a, b| a[1] <=> b[1] } end - private - # Convert a name into a label. - def labelize(name) + def self.labelize(name) name.to_s.capitalize.gsub("_", " ") end end diff --git a/spec/integration/configurer_spec.rb b/spec/integration/configurer_spec.rb index 9a8b66fe4..780c9bbd0 100755 --- a/spec/integration/configurer_spec.rb +++ b/spec/integration/configurer_spec.rb @@ -5,6 +5,8 @@ require File.dirname(__FILE__) + '/../spec_helper' require 'puppet/configurer' describe Puppet::Configurer do + include PuppetSpec::Files + describe "when downloading plugins" do it "should use the :pluginsignore setting, split on whitespace, for ignoring remote files" do resource = Puppet::Type.type(:notify).new :name => "yay" @@ -17,19 +19,50 @@ describe Puppet::Configurer do end describe "when running" do - it "should send a transaction report with valid data" do - catalog = Puppet::Resource::Catalog.new - catalog.add_resource(Puppet::Type.type(:notify).new(:title => "testing")) + before(:each) do + @catalog = Puppet::Resource::Catalog.new + @catalog.add_resource(Puppet::Type.type(:notify).new(:title => "testing")) - configurer = Puppet::Configurer.new + # Make sure we don't try to persist the local state after the transaction ran, + # because it will fail during test (the state file is in an not existing directory) + # and we need the transaction to be successful to be able to produce a summary report + @catalog.host_config = false + + @configurer = Puppet::Configurer.new + end + + it "should send a transaction report with valid data" do + @configurer.stubs(:save_last_run_summary) Puppet::Transaction::Report.indirection.expects(:save).with do |x, report| report.time.class == Time and report.logs.length > 0 end Puppet[:report] = true - configurer.run :catalog => catalog + @configurer.run :catalog => @catalog + end + + it "should save a correct last run summary" do + report = Puppet::Transaction::Report.new("apply") + report.stubs(:save) + + Puppet[:lastrunfile] = tmpfile("lastrunfile") + Puppet[:report] = true + + @configurer.run :catalog => @catalog, :report => report + + summary = nil + File.open(Puppet[:lastrunfile], "r") do |fd| + summary = YAML.load(fd.read) + end + + summary.should be_a(Hash) + %w{time changes events resources}.each do |key| + summary.should be_key(key) + end + summary["time"].should be_key("notify") + summary["time"]["last_run"].should >= Time.now.tv_sec end end end diff --git a/spec/integration/transaction_spec.rb b/spec/integration/transaction_spec.rb index d5478d7a7..66a049efe 100755 --- a/spec/integration/transaction_spec.rb +++ b/spec/integration/transaction_spec.rb @@ -107,29 +107,23 @@ describe Puppet::Transaction do file1 = tmpfile("file1") file2 = tmpfile("file2") - file = Puppet::Type.type(:file).new( - - :path => path, - + file = Puppet::Type.type(:file).new( + :path => path, :ensure => "file" ) - exec1 = Puppet::Type.type(:exec).new( - - :path => ENV["PATH"], + exec1 = Puppet::Type.type(:exec).new( + :path => ENV["PATH"], :command => "touch #{file1}", :refreshonly => true, - - :subscribe => Puppet::Resource.new(:file, path) + :subscribe => Puppet::Resource.new(:file, path) ) - exec2 = Puppet::Type.type(:exec).new( - - :path => ENV["PATH"], - :command => "touch #{file2}", + exec2 = Puppet::Type.type(:exec).new( + :path => ENV["PATH"], + :command => "touch #{file2}", :refreshonly => true, - - :subscribe => Puppet::Resource.new(:file, path) + :subscribe => Puppet::Resource.new(:file, path) ) catalog = mk_catalog(file, exec1, exec2) @@ -141,33 +135,26 @@ describe Puppet::Transaction do it "should not let one failed refresh result in other refreshes failing" do path = tmpfile("path") newfile = tmpfile("file") - - file = Puppet::Type.type(:file).new( - + file = Puppet::Type.type(:file).new( :path => path, - :ensure => "file" ) - exec1 = Puppet::Type.type(:exec).new( - + exec1 = Puppet::Type.type(:exec).new( :path => ENV["PATH"], :command => "touch /this/cannot/possibly/exist", :logoutput => true, :refreshonly => true, :subscribe => file, - :title => "one" ) - exec2 = Puppet::Type.type(:exec).new( - + exec2 = Puppet::Type.type(:exec).new( :path => ENV["PATH"], :command => "touch #{newfile}", :logoutput => true, :refreshonly => true, :subscribe => [file, exec1], - :title => "two" ) @@ -184,22 +171,18 @@ describe Puppet::Transaction do Puppet[:ignoreschedules] = false - file = Puppet::Type.type(:file).new( - + file = Puppet::Type.type(:file).new( :name => tmpfile("file"), - :ensure => "file", :backup => false ) fname = tmpfile("exec") - exec = Puppet::Type.type(:exec).new( - + exec = Puppet::Type.type(:exec).new( :name => "touch #{fname}", :path => "/usr/bin:/bin", :schedule => "monthly", - :subscribe => Puppet::Resource.new("file", file.name) ) @@ -236,29 +219,21 @@ describe Puppet::Transaction do it "should not attempt to evaluate resources with failed dependencies" do - exec = Puppet::Type.type(:exec).new( - + exec = Puppet::Type.type(:exec).new( :command => "/bin/mkdir /this/path/cannot/possibly/exit", - :title => "mkdir" ) - - file1 = Puppet::Type.type(:file).new( - + file1 = Puppet::Type.type(:file).new( :title => "file1", :path => tmpfile("file1"), - :require => exec, :ensure => :file ) - - file2 = Puppet::Type.type(:file).new( - + file2 = Puppet::Type.type(:file).new( :title => "file2", :path => tmpfile("file2"), - :require => file1, :ensure => :file ) @@ -270,6 +245,32 @@ describe Puppet::Transaction do FileTest.should_not be_exists(file2[:path]) end + it "should not trigger subscribing resources on failure" do + file1 = tmpfile("file1") + file2 = tmpfile("file2") + + create_file1 = Puppet::Type.type(:exec).new( + :command => "/usr/bin/touch #{file1}" + ) + + exec = Puppet::Type.type(:exec).new( + :command => "/bin/mkdir /this/path/cannot/possibly/exit", + :title => "mkdir", + :notify => create_file1 + ) + + create_file2 = Puppet::Type.type(:exec).new( + :command => "/usr/bin/touch #{file2}", + :subscribe => exec + ) + + catalog = mk_catalog(exec, create_file1, create_file2) + catalog.apply + + FileTest.should_not be_exists(file1) + FileTest.should_not be_exists(file2) + end + # #801 -- resources only checked in noop should be rescheduled immediately. it "should immediately reschedule noop resources" do Puppet::Type.type(:schedule).mkdefaultschedules diff --git a/spec/lib/puppet_spec/verbose.rb b/spec/lib/puppet_spec/verbose.rb new file mode 100644 index 000000000..d9834f2d7 --- /dev/null +++ b/spec/lib/puppet_spec/verbose.rb @@ -0,0 +1,9 @@ +# Support code for running stuff with warnings disabled. +module Kernel + def with_verbose_disabled + verbose, $VERBOSE = $VERBOSE, nil + result = yield + $VERBOSE = verbose + return result + end +end diff --git a/spec/shared_behaviours/path_parameters.rb b/spec/shared_behaviours/path_parameters.rb new file mode 100644 index 000000000..b5a907900 --- /dev/null +++ b/spec/shared_behaviours/path_parameters.rb @@ -0,0 +1,185 @@ +# In order to use this correctly you must define a method to get an instance +# of the type being tested, so that this code can remain generic: +# +# it_should_behave_like "all path parameters", :path do +# def instance(path) +# Puppet::Type.type(:example).new( +# :name => 'foo', :require => 'bar', :path_param => path +# ) +# end +# +# That method will be invoked for each test to create the instance that we +# subsequently test through the system; you should ensure that the minimum of +# possible attributes are set to keep the tests clean. +# +# You must also pass the symbolic name of the parameter being tested to the +# block, and optionally can pass a hash of additional options to the block. +# +# The known options are: +# :array :: boolean, does this support arrays of paths, default true. + +shared_examples_for "all pathname parameters with arrays" do |win32| + path_types = { + "unix absolute" => "/foo/bar", + "unix relative" => "foo/bar", + "win32 absolute" => %q{\foo\bar}, + "win32 relative" => %q{foo\bar}, + "drive absolute" => %q{c:\foo\bar}, + "drive relative" => %q{c:foo\bar} + } + + describe "when given an array of paths" do + (1..path_types.length).each do |n| + path_types.keys.combination(n) do |set| + data = path_types.collect { |k, v| set.member?(k) ? v : nil } .compact + reject = true + only_absolute = set.find { |k| k =~ /relative/ } .nil? + only_unix = set.reject { |k| k =~ /unix/ } .length == 0 + + if only_absolute and (only_unix or win32) then + reject = false + end + + it "should #{reject ? 'reject' : 'accept'} #{set.join(", ")}" do + if reject then + expect { instance(data) }. + should raise_error Puppet::Error, /fully qualified/ + else + instance = instance(data) + instance[@param].should == data + end + end + + it "should #{reject ? 'reject' : 'accept'} #{set.join(", ")} doubled" do + if reject then + expect { instance(data + data) }. + should raise_error Puppet::Error, /fully qualified/ + else + instance = instance(data + data) + instance[@param].should == (data + data) + end + end + end + end + end +end + + +shared_examples_for "all path parameters" do |param, options| + # Extract and process options to the block. + options ||= {} + array = options[:array].nil? ? true : options.delete(:array) + if options.keys.length > 0 then + fail "unknown options for 'all path parameters': " + + options.keys.sort.join(', ') + end + + def instance(path) + fail "we didn't implement the 'instance(path)' method in the it_should_behave_like block" + end + + ######################################################################## + # The actual testing code... + before :all do + @param = param + end + + before :each do + @file_separator = File::SEPARATOR + end + after :each do + with_verbose_disabled do + verbose, $VERBOSE = $VERBOSE, nil + File::SEPARATOR = @file_separator + $VERBOSE = verbose + end + end + + describe "on a Unix-like platform it" do + before :each do + with_verbose_disabled do + File::SEPARATOR = '/' + end + Puppet.features.stubs(:microsoft_windows?).returns(false) + Puppet.features.stubs(:posix?).returns(true) + end + + if array then + it_should_behave_like "all pathname parameters with arrays", false + end + + it "should accept a fully qualified path" do + path = File.join('', 'foo') + instance = instance(path) + instance[@param].should == path + end + + it "should give a useful error when the path is not absolute" do + path = 'foo' + expect { instance(path) }. + should raise_error Puppet::Error, /fully qualified/ + end + + { "Unix" => '/', "Win32" => '\\' }.each do |style, slash| + %w{q Q a A z Z c C}.sort.each do |drive| + it "should reject drive letter '#{drive}' with #{style} path separators" do + path = "#{drive}:#{slash}Program Files" + expect { instance(path) }. + should raise_error Puppet::Error, /fully qualified/ + end + end + end + end + + describe "on a Windows-like platform it" do + before :each do + with_verbose_disabled do + File::SEPARATOR = '\\' + end + Puppet.features.stubs(:microsoft_windows?).returns(true) + Puppet.features.stubs(:posix?).returns(false) + end + + if array then + it_should_behave_like "all pathname parameters with arrays", true + end + + it "should accept a fully qualified path" do + path = File.join('', 'foo') + instance = instance(path) + instance[@param].should == path + end + + it "should give a useful error when the path is not absolute" do + path = 'foo' + expect { instance(path) }. + should raise_error Puppet::Error, /fully qualified/ + end + + it "also accepts Unix style path separators" do + path = '/Program Files' + instance = instance(path) + instance[@param].should == path + end + + { "Unix" => '/', "Win32" => '\\' }.each do |style, slash| + %w{q Q a A z Z c C}.sort.each do |drive| + it "should accept drive letter '#{drive}' with #{style} path separators " do + path = "#{drive}:#{slash}Program Files" + instance = instance(path) + instance[@param].should == path + end + end + end + + { "UNC paths" => %q{\\foo\bar}, + "unparsed local paths" => %q{\\?\c:\foo}, + "unparsed UNC paths" => %q{\\?\foo\bar} + }.each do |name, path| + it "should accept #{name} as absolute" do + instance = instance(path) + instance[@param].should == path + end + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index ae4edb2d9..505a8f973 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -23,10 +23,16 @@ end module PuppetTest end +require 'pathname' +require 'lib/puppet_spec/verbose' require 'lib/puppet_spec/files' require 'monkey_patches/alias_should_to_must' require 'monkey_patches/publicize_methods' +Pathname.glob("#{dir}/shared_behaviours/**/*.rb") do |behaviour| + require behaviour.relative_path_from(Pathname.new(dir)) +end + RSpec.configure do |config| config.mock_with :mocha diff --git a/spec/unit/application/agent_spec.rb b/spec/unit/application/agent_spec.rb index 8f498d4ba..c9d9a4509 100755 --- a/spec/unit/application/agent_spec.rb +++ b/spec/unit/application/agent_spec.rb @@ -179,6 +179,7 @@ describe Puppet::Application::Agent do Puppet.settings.stubs(:print_config) Puppet::SSL::Host.stubs(:ca_location=) Puppet::Transaction::Report.stubs(:terminus_class=) + Puppet::Transaction::Report.stubs(:cache_class=) Puppet::Resource::Catalog.stubs(:terminus_class=) Puppet::Resource::Catalog.stubs(:cache_class=) Puppet::Node::Facts.stubs(:terminus_class=) @@ -309,6 +310,12 @@ describe Puppet::Application::Agent do @puppetd.setup end + it "should tell the report handler to cache locally as yaml" do + Puppet::Transaction::Report.expects(:cache_class=).with(:yaml) + + @puppetd.setup + end + it "should change the catalog_terminus setting to 'rest'" do Puppet.expects(:[]=).with(:catalog_terminus, :rest) @puppetd.setup diff --git a/spec/unit/application/apply_spec.rb b/spec/unit/application/apply_spec.rb index d4f39abe0..67edd4ed7 100755 --- a/spec/unit/application/apply_spec.rb +++ b/spec/unit/application/apply_spec.rb @@ -56,6 +56,7 @@ describe Puppet::Application::Apply do Puppet.stubs(:parse_config) Puppet::FileBucket::Dipper.stubs(:new) STDIN.stubs(:read) + Puppet::Transaction::Report.stubs(:cache_class=) @apply.options.stubs(:[]).with(any_parameters) end @@ -113,6 +114,11 @@ describe Puppet::Application::Apply do lambda { @apply.setup }.should raise_error(SystemExit) end + it "should tell the report handler to cache locally as yaml" do + Puppet::Transaction::Report.expects(:cache_class=).with(:yaml) + + @apply.setup + end end describe "when executing" do diff --git a/spec/unit/application/master_spec.rb b/spec/unit/application/master_spec.rb index 074249a4d..d99b22ded 100644 --- a/spec/unit/application/master_spec.rb +++ b/spec/unit/application/master_spec.rb @@ -176,8 +176,8 @@ describe Puppet::Application::Master do lambda { @master.setup }.should raise_error(SystemExit) end - it "should tell Puppet.settings to use :main,:ssl and :master category" do - Puppet.settings.expects(:use).with(:main,:master,:ssl) + it "should tell Puppet.settings to use :main,:ssl,:master and :metrics category" do + Puppet.settings.expects(:use).with(:main,:master,:ssl,:metrics) @master.setup end diff --git a/spec/unit/configurer_spec.rb b/spec/unit/configurer_spec.rb index cf73a3706..a4b627c08 100755 --- a/spec/unit/configurer_spec.rb +++ b/spec/unit/configurer_spec.rb @@ -81,6 +81,7 @@ describe Puppet::Configurer, "when executing a catalog run" do @catalog = Puppet::Resource::Catalog.new @catalog.stubs(:apply) @agent.stubs(:retrieve_catalog).returns @catalog + @agent.stubs(:save_last_run_summary) Puppet::Util::Log.stubs(:newdestination) Puppet::Util::Log.stubs(:close) @@ -225,9 +226,12 @@ describe Puppet::Configurer, "when executing a catalog run" do end describe Puppet::Configurer, "when sending a report" do + include PuppetSpec::Files + before do Puppet.settings.stubs(:use).returns(true) @configurer = Puppet::Configurer.new + Puppet[:lastrunfile] = tmpfile('last_run_file') @report = Puppet::Transaction::Report.new("apply") @trans = stub 'transaction' @@ -268,6 +272,20 @@ describe Puppet::Configurer, "when sending a report" do @configurer.send_report(@report, nil) end + it "should save the last run summary if reporting is enabled" do + Puppet.settings[:report] = true + + @configurer.expects(:save_last_run_summary).with(@report) + @configurer.send_report(@report, nil) + end + + it "should save the last run summary if reporting is disabled" do + Puppet.settings[:report] = false + + @configurer.expects(:save_last_run_summary).with(@report) + @configurer.send_report(@report, nil) + end + it "should log but not fail if saving the report fails" do Puppet.settings[:report] = true @@ -278,6 +296,36 @@ describe Puppet::Configurer, "when sending a report" do end end +describe Puppet::Configurer, "when saving the summary report file" do + before do + Puppet.settings.stubs(:use).returns(true) + @configurer = Puppet::Configurer.new + + @report = stub 'report' + @trans = stub 'transaction' + @lastrunfd = stub 'lastrunfd' + Puppet::Util::FileLocking.stubs(:writelock).yields(@lastrunfd) + end + + it "should write the raw summary to the lastrunfile setting value" do + Puppet::Util::FileLocking.expects(:writelock).with(Puppet[:lastrunfile], 0660) + @configurer.save_last_run_summary(@report) + end + + it "should write the raw summary as yaml" do + @report.expects(:raw_summary).returns("summary") + @lastrunfd.expects(:print).with(YAML.dump("summary")) + @configurer.save_last_run_summary(@report) + end + + it "should log but not fail if saving the last run summary fails" do + Puppet::Util::FileLocking.expects(:writelock).raises "exception" + Puppet.expects(:err) + lambda { @configurer.save_last_run_summary(@report) }.should_not raise_error + end + +end + describe Puppet::Configurer, "when retrieving a catalog" do before do Puppet.settings.stubs(:use).returns(true) diff --git a/spec/unit/file_serving/fileset_spec.rb b/spec/unit/file_serving/fileset_spec.rb index ecc77812c..149c68c4a 100755 --- a/spec/unit/file_serving/fileset_spec.rb +++ b/spec/unit/file_serving/fileset_spec.rb @@ -13,6 +13,14 @@ describe Puppet::FileServing::Fileset, " when initializing" do proc { Puppet::FileServing::Fileset.new("some/file") }.should raise_error(ArgumentError) end + it "should not fail if the path is fully qualified, with a trailing separator" do + path = "/some/path/with/trailing/separator" + path_with_separator = "#{path}#{File::SEPARATOR}" + File.stubs(:lstat).with(path).returns stub('stat') + fileset = Puppet::FileServing::Fileset.new(path_with_separator) + fileset.path.should == path + end + it "should fail if its path does not exist" do File.expects(:lstat).with("/some/file").returns nil proc { Puppet::FileServing::Fileset.new("/some/file") }.should raise_error(ArgumentError) diff --git a/spec/unit/indirector/exec_spec.rb b/spec/unit/indirector/exec_spec.rb index acad1ea93..de37f2775 100755 --- a/spec/unit/indirector/exec_spec.rb +++ b/spec/unit/indirector/exec_spec.rb @@ -47,10 +47,9 @@ describe Puppet::Indirector::Exec do @searcher.find(@request).should be_nil end - it "should return nil and log an error if there's an execution failure" do + it "should raise an exception if there's an execution failure" do @searcher.expects(:execute).with(%w{/echo foo}).raises(Puppet::ExecutionFailure.new("message")) - Puppet.expects(:err) - @searcher.find(@request).should be_nil + lambda {@searcher.find(@request)}.should raise_exception(Puppet::Error, 'Failed to find foo via exec: message') end end diff --git a/spec/unit/indirector/report/yaml_spec.rb b/spec/unit/indirector/report/yaml_spec.rb new file mode 100644 index 000000000..610c9ae43 --- /dev/null +++ b/spec/unit/indirector/report/yaml_spec.rb @@ -0,0 +1,38 @@ +#!/usr/bin/env ruby + +require File.dirname(__FILE__) + '/../../../spec_helper' + +require 'puppet/transaction/report' +require 'puppet/indirector/report/yaml' + +describe Puppet::Transaction::Report::Yaml do + it "should be a subclass of the Yaml terminus" do + Puppet::Transaction::Report::Yaml.superclass.should equal(Puppet::Indirector::Yaml) + end + + it "should have documentation" do + Puppet::Transaction::Report::Yaml.doc.should_not be_nil + end + + it "should be registered with the report indirection" do + indirection = Puppet::Indirector::Indirection.instance(:report) + Puppet::Transaction::Report::Yaml.indirection.should equal(indirection) + end + + it "should have its name set to :yaml" do + Puppet::Transaction::Report::Yaml.name.should == :yaml + end + + it "should inconditionnally save/load from the --lastrunreport setting" do + indirection = stub 'indirection', :name => :my_yaml, :register_terminus_type => nil + Puppet::Indirector::Indirection.stubs(:instance).with(:my_yaml).returns(indirection) + store_class = Class.new(Puppet::Transaction::Report::Yaml) do + def self.to_s + "MyYaml::MyType" + end + end + store = store_class.new + + store.path(:me).should == Puppet[:lastrunreport] + end +end diff --git a/spec/unit/node/environment_spec.rb b/spec/unit/node/environment_spec.rb index 6edcce56c..6109007b9 100755 --- a/spec/unit/node/environment_spec.rb +++ b/spec/unit/node/environment_spec.rb @@ -85,28 +85,29 @@ describe Puppet::Node::Environment do @env.known_resource_types.should equal(@collection) end - it "should give to all threads the same collection if it didn't change" do - Puppet::Resource::TypeCollection.expects(:new).with(@env).returns @collection - @env.known_resource_types + it "should give to all threads using the same environment the same collection if the collection isn't stale" do + original_thread_type_collection = Puppet::Resource::TypeCollection.new(@env) + Puppet::Resource::TypeCollection.expects(:new).with(@env).returns original_thread_type_collection + @env.known_resource_types.should equal(original_thread_type_collection) + + original_thread_type_collection.expects(:require_reparse?).returns(false) + Puppet::Resource::TypeCollection.stubs(:new).with(@env).returns @collection t = Thread.new { - @env.known_resource_types.should equal(@collection) + @env.known_resource_types.should equal(original_thread_type_collection) } t.join end - it "should give to new threads a new collection if it isn't stale" do - Puppet::Resource::TypeCollection.expects(:new).with(@env).returns @collection - @env.known_resource_types.expects(:stale?).returns(true) - - Puppet::Resource::TypeCollection.expects(:new).returns @collection + it "should generate a new TypeCollection if the current one requires reparsing" do + old_type_collection = @env.known_resource_types + old_type_collection.stubs(:require_reparse?).returns true + Thread.current[:known_resource_types] = nil + new_type_collection = @env.known_resource_types - t = Thread.new { - @env.known_resource_types.should equal(@collection) - } - t.join + new_type_collection.should be_a Puppet::Resource::TypeCollection + new_type_collection.should_not equal(old_type_collection) end - end [:modulepath, :manifestdir].each do |setting| diff --git a/spec/unit/parameter/path_spec.rb b/spec/unit/parameter/path_spec.rb new file mode 100644 index 000000000..08a26de33 --- /dev/null +++ b/spec/unit/parameter/path_spec.rb @@ -0,0 +1,24 @@ +#!/usr/bin/env ruby +require File.expand_path(File.join(File.dirname(__FILE__), '../../spec_helper')) + +require 'puppet/parameter/path' + +[false, true].each do |arrays| + describe "Puppet::Parameter::Path with arrays #{arrays}" do + it_should_behave_like "all path parameters", :path, :array => arrays do + # The new type allows us a test that is guaranteed to go direct to our + # validation code, without passing through any "real type" overrides or + # whatever on the way. + Puppet::newtype(:test_puppet_parameter_path) do + newparam(:path, :parent => Puppet::Parameter::Path, :arrays => arrays) do + isnamevar + accept_arrays arrays + end + end + + def instance(path) + Puppet::Type.type(:test_puppet_parameter_path).new(:path => path) + end + end + end +end diff --git a/spec/unit/parser/compiler_spec.rb b/spec/unit/parser/compiler_spec.rb index 687f2ecb9..261cfdec1 100755 --- a/spec/unit/parser/compiler_spec.rb +++ b/spec/unit/parser/compiler_spec.rb @@ -581,12 +581,11 @@ describe Puppet::Parser::Compiler do @scope.expects(:find_hostclass).with("notfound").returns(nil) @compiler.evaluate_classes(%w{notfound}, @scope) end - # I wish it would fail it "should log when it can't find class" do klasses = {'foo'=>nil} @node.classes = klasses @compiler.topscope.stubs(:find_hostclass).with('foo').returns(nil) - Puppet.expects(:info).with('Could not find class foo for testnode') + Puppet.expects(:warning).with('Could not find class foo for testnode') @compiler.compile end end diff --git a/spec/unit/parser/lexer_spec.rb b/spec/unit/parser/lexer_spec.rb index 4ef242cf5..d144504c5 100755 --- a/spec/unit/parser/lexer_spec.rb +++ b/spec/unit/parser/lexer_spec.rb @@ -679,3 +679,15 @@ describe "Puppet::Parser::Lexer in the old tests when lexing example files" do end end end + +describe "when trying to lex an non-existent file" do + include PuppetSpec::Files + + it "should return an empty list of tokens" do + lexer = Puppet::Parser::Lexer.new + lexer.file = nofile = tmpfile('lexer') + File.exists?(nofile).should == false + + lexer.fullscan.should == [[false,false]] + end +end diff --git a/spec/unit/provider/exec/posix_spec.rb b/spec/unit/provider/exec/posix_spec.rb new file mode 100755 index 000000000..d02099250 --- /dev/null +++ b/spec/unit/provider/exec/posix_spec.rb @@ -0,0 +1,120 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../../spec_helper' + +provider_class = Puppet::Type.type(:exec).provider(:posix) + +describe provider_class do + before :each do + @resource = Puppet::Resource.new(:exec, 'foo') + @provider = provider_class.new(@resource) + end + + ["posix", "microsoft_windows"].each do |feature| + describe "when in #{feature} environment" do + before :each do + if feature == "microsoft_windows" + Puppet.features.stubs(:microsoft_windows?).returns(true) + Puppet.features.stubs(:posix?).returns(false) + else + Puppet.features.stubs(:posix?).returns(true) + Puppet.features.stubs(:microsoft_windows?).returns(false) + end + end + + describe "#validatecmd" do + it "should fail if no path is specified and the command is not fully qualified" do + lambda { @provider.validatecmd("foo") }.should raise_error( + Puppet::Error, + "'foo' is not qualified and no path was specified. Please qualify the command or specify a path." + ) + end + + it "should pass if a path is given" do + @provider.resource[:path] = ['/bogus/bin'] + @provider.validatecmd("../foo") + end + + it "should pass if command is fully qualifed" do + @provider.resource[:path] = ['/bogus/bin'] + @provider.validatecmd("/bin/blah/foo") + end + end + + describe "#run" do + it "should fail if no path is specified and command does not exist" do + lambda { @provider.run("foo") }.should raise_error(ArgumentError, "Could not find command 'foo'") + end + + it "should fail if the command isn't in the path" do + @provider.resource[:path] = ['/bogus/bin'] + lambda { @provider.run("foo") }.should raise_error(ArgumentError, "Could not find command 'foo'") + end + + it "should fail if the command isn't executable" do + @provider.resource[:path] = ['/bogus/bin'] + File.stubs(:exists?).with("foo").returns(true) + + lambda { @provider.run("foo") }.should raise_error(ArgumentError, "'foo' is not executable") + end + + it "should not be able to execute shell builtins" do + @provider.resource[:path] = ['/bin'] + lambda { @provider.run("cd ..") }.should raise_error(ArgumentError, "Could not find command 'cd'") + end + + it "should execute the command if the command given includes arguments or subcommands" do + @provider.resource[:path] = ['/bogus/bin'] + File.stubs(:exists?).returns(false) + File.stubs(:exists?).with("foo").returns(true) + File.stubs(:executable?).with("foo").returns(true) + + Puppet::Util.expects(:execute).with() { |command, arguments| (command == ['foo bar --sillyarg=true --blah']) && (arguments.is_a? Hash) } + @provider.run("foo bar --sillyarg=true --blah") + end + + it "should fail if quoted command doesn't exist" do + @provider.resource[:path] = ['/bogus/bin'] + File.stubs(:exists?).returns(false) + File.stubs(:exists?).with("foo").returns(true) + File.stubs(:executable?).with("foo").returns(true) + + lambda { @provider.run('"foo bar --sillyarg=true --blah"') }.should raise_error(ArgumentError, "Could not find command 'foo bar --sillyarg=true --blah'") + end + + it "should execute the command if it finds it in the path and is executable" do + @provider.resource[:path] = ['/bogus/bin'] + File.stubs(:exists?).with("foo").returns(true) + File.stubs(:executable?).with("foo").returns(true) + Puppet::Util.expects(:execute).with() { |command, arguments| (command == ['foo']) && (arguments.is_a? Hash) } + + @provider.run("foo") + end + + if feature == "microsoft_windows" + [".exe", ".ps1", ".bat", ".com", ""].each do |extension| + it "should check file extension #{extension} when it can't find the executable" do + @provider.resource[:path] = ['/bogus/bin'] + File.stubs(:exists?).returns(false) + File.stubs(:exists?).with("/bogus/bin/foo#{extension}").returns(true) + File.stubs(:executable?).with("foo").returns(true) + Puppet::Util.expects(:execute).with() { |command, arguments| (command == ['foo']) && (arguments.is_a? Hash) } + + @provider.run("foo") + end + end + end + + it "should warn if you're overriding something in environment" do + @provider.resource[:environment] = ['WHATEVER=/something/else', 'WHATEVER=/foo'] + File.stubs(:exists?).returns(false) + File.stubs(:exists?).with("foo").returns(true) + File.stubs(:executable?).with("foo").returns(true) + + Puppet::Util.expects(:execute).with() { |command, arguments| (command == ['foo']) && (arguments.is_a? Hash) } + @provider.run("foo") + @logs.map {|l| "#{l.level}: #{l.message}" }.should == ["warning: Overriding environment setting 'WHATEVER' with '/foo'"] + end + end + end + end +end diff --git a/spec/unit/provider/exec/shell_spec.rb b/spec/unit/provider/exec/shell_spec.rb new file mode 100644 index 000000000..4bae354c9 --- /dev/null +++ b/spec/unit/provider/exec/shell_spec.rb @@ -0,0 +1,50 @@ +#!/usr/bin/env ruby +require File.dirname(__FILE__) + '/../../../spec_helper' + +provider_class = Puppet::Type.type(:exec).provider(:shell) + +describe provider_class do + before :each do + @resource = Puppet::Resource.new(:exec, 'foo') + @provider = provider_class.new(@resource) + end + + describe "#run" do + it "should be able to run builtin shell commands" do + output, status = @provider.run("if [ 1 = 1 ]; then echo 'blah'; fi") + status.exitstatus.should == 0 + output.should == "blah\n" + end + + it "should be able to run commands with single quotes in them" do + output, status = @provider.run("echo 'foo bar'") + status.exitstatus.should == 0 + output.should == "foo bar\n" + end + + it "should be able to run commands with double quotes in them" do + output, status = @provider.run('echo "foo bar"') + status.exitstatus.should == 0 + output.should == "foo bar\n" + end + + it "should be able to run multiple commands separated by a semicolon" do + output, status = @provider.run("echo 'foo' ; echo 'bar'") + status.exitstatus.should == 0 + output.should == "foo\nbar\n" + end + + it "should be able to read values from the environment parameter" do + @resource[:environment] = "FOO=bar" + output, status = @provider.run("echo $FOO") + status.exitstatus.should == 0 + output.should == "bar\n" + end + end + + describe "#validatecmd" do + it "should always return true because builtins don't need path or to be fully qualified" do + @provider.validatecmd('whateverdoesntmatter').should == true + end + end +end diff --git a/spec/unit/provider/package/gem_spec.rb b/spec/unit/provider/package/gem_spec.rb index 063e1474b..32c067a60 100644 --- a/spec/unit/provider/package/gem_spec.rb +++ b/spec/unit/provider/package/gem_spec.rb @@ -42,8 +42,18 @@ describe provider_class do @provider.install end + it "should specify that documentation should not be included" do + @provider.expects(:execute).with { |args| args[3] == "--no-rdoc" }.returns "" + @provider.install + end + + it "should specify that RI should not be included" do + @provider.expects(:execute).with { |args| args[4] == "--no-ri" }.returns "" + @provider.install + end + it "should specify the package name" do - @provider.expects(:execute).with { |args| args[3] == "myresource" }.returns "" + @provider.expects(:execute).with { |args| args[5] == "myresource" }.returns "" @provider.install end diff --git a/spec/unit/provider/service/debian_spec.rb b/spec/unit/provider/service/debian_spec.rb index 8dee2ee94..440d4491b 100755 --- a/spec/unit/provider/service/debian_spec.rb +++ b/spec/unit/provider/service/debian_spec.rb @@ -52,8 +52,20 @@ describe provider_class do end describe "when disabling" do - it "should call update-rc.d twice" do - @provider.expects(:update_rc).twice + it "should be able to disable services with newer sysv-rc versions" do + @provider.stubs(:`).with("dpkg --compare-versions $(dpkg-query -W --showformat '${Version}' sysv-rc) ge 2.88 ; echo $?").returns "0" + + @provider.expects(:update_rc).with(@resource[:name], "disable") + + @provider.disable + end + + it "should be able to enable services with older sysv-rc versions" do + @provider.stubs(:`).with("dpkg --compare-versions $(dpkg-query -W --showformat '${Version}' sysv-rc) ge 2.88 ; echo $?").returns "1" + + @provider.expects(:update_rc).with("-f", @resource[:name], "remove") + @provider.expects(:update_rc).with(@resource[:name], "stop", "00", "1", "2", "3", "4", "5", "6", ".") + @provider.disable end end diff --git a/spec/unit/resource/type_collection_spec.rb b/spec/unit/resource/type_collection_spec.rb index ff4c22234..1576c0615 100644 --- a/spec/unit/resource/type_collection_spec.rb +++ b/spec/unit/resource/type_collection_spec.rb @@ -6,6 +6,7 @@ require 'puppet/resource/type_collection' require 'puppet/resource/type' describe Puppet::Resource::TypeCollection do + include PuppetSpec::Files before do @instance = Puppet::Resource::Type.new(:hostclass, "foo") @code = Puppet::Resource::TypeCollection.new("env") @@ -276,7 +277,7 @@ describe Puppet::Resource::TypeCollection do end end - + it "should not look in the local scope for classes when the name is qualified" do @loader = Puppet::Resource::TypeCollection.new("env") @loader.add Puppet::Resource::Type.new(:hostclass, "foo::bar") @@ -386,16 +387,11 @@ describe Puppet::Resource::TypeCollection do describe "when performing initial import" do before do - @parser = stub 'parser', :file= => nil, :string => nil, :parse => nil + @parser = Puppet::Parser::Parser.new("test") Puppet::Parser::Parser.stubs(:new).returns @parser @code = Puppet::Resource::TypeCollection.new("env") end - it "should create a new parser instance" do - Puppet::Parser::Parser.expects(:new).returns @parser - @code.perform_initial_import - end - it "should set the parser's string to the 'code' setting and parse if code is available" do Puppet.settings[:code] = "my code" @parser.expects(:string=).with "my code" @@ -404,28 +400,36 @@ describe Puppet::Resource::TypeCollection do end it "should set the parser's file to the 'manifest' setting and parse if no code is available and the manifest is available" do - File.stubs(:expand_path).with("/my/file").returns "/my/file" - File.expects(:exist?).with("/my/file").returns true - Puppet.settings[:manifest] = "/my/file" - @parser.expects(:file=).with "/my/file" + filename = tmpfile('myfile') + File.open(filename, 'w'){|f| } + Puppet.settings[:manifest] = filename + @parser.expects(:file=).with filename @parser.expects(:parse) @code.perform_initial_import end - it "should not attempt to load a manifest if none is present" do - File.stubs(:expand_path).with("/my/file").returns "/my/file" - File.expects(:exist?).with("/my/file").returns false - Puppet.settings[:manifest] = "/my/file" - @parser.expects(:file=).never - @parser.expects(:parse).never + it "should pass the manifest file to the parser even if it does not exist on disk" do + filename = tmpfile('myfile') + Puppet.settings[:code] = "" + Puppet.settings[:manifest] = filename + @parser.expects(:file=).with(filename).once + @parser.expects(:parse).once @code.perform_initial_import end it "should fail helpfully if there is an error importing" do File.stubs(:exist?).returns true @parser.expects(:parse).raises ArgumentError + @parser.stubs(:file=) lambda { @code.perform_initial_import }.should raise_error(Puppet::Error) end + + it "should mark the type collection as needing a reparse when there is an error parsing" do + @parser.expects(:parse).raises Puppet::ParseError.new("Syntax error at ...") + + lambda { @code.perform_initial_import }.should raise_error(Puppet::Error, /Syntax error at .../) + @code.require_reparse?.should be_true + end end describe "when determining the configuration version" do diff --git a/spec/unit/transaction/report_spec.rb b/spec/unit/transaction/report_spec.rb index 766d4f14d..81efa340e 100755 --- a/spec/unit/transaction/report_spec.rb +++ b/spec/unit/transaction/report_spec.rb @@ -273,8 +273,19 @@ describe Puppet::Transaction::Report do @report.finalize_report end - %w{Changes Total Resources}.each do |main| - it "should include information on #{main} in the summary" do + %w{changes time resources events}.each do |main| + it "should include the key #{main} in the raw summary hash" do + @report.raw_summary.should be_key main + end + end + + it "should include the last run time in the raw summary hash" do + Time.stubs(:now).returns(Time.utc(2010,11,10,12,0,24)) + @report.raw_summary["time"]["last_run"].should == 1289390424 + end + + %w{Changes Total Resources Time Events}.each do |main| + it "should include information on #{main} in the textual summary" do @report.summary.should be_include(main) end end diff --git a/spec/unit/type/cron_spec.rb b/spec/unit/type/cron_spec.rb index 03817d20e..f985cdd09 100755 --- a/spec/unit/type/cron_spec.rb +++ b/spec/unit/type/cron_spec.rb @@ -4,30 +4,478 @@ Dir.chdir(File.dirname(__FILE__)) { (s = lambda { |f| File.exist?(f) ? require(f describe Puppet::Type.type(:cron) do before do - @cron = Puppet::Type.type(:cron).new( :name => "foo" ) - end + @class = Puppet::Type.type(:cron) + + # Init a fake provider + @provider_class = stub 'provider_class', :ancestors => [], :name => 'fake', :suitable? => true, :supports_parameter? => true + @class.stubs(:defaultprovider).returns @provider_class + @class.stubs(:provider).returns @provider_class + + @provider = stub 'provider', :class => @provider_class, :clean => nil + @provider.stubs(:is_a?).returns false + @provider_class.stubs(:new).returns @provider - it "it should accept an :environment that looks like a path" do - lambda do - @cron[:environment] = 'PATH=/bin:/usr/bin:/usr/sbin' - end.should_not raise_error + @cron = @class.new( :name => "foo" ) end - it "should not accept environment variables that do not contain '='" do - lambda do - @cron[:environment] = "INVALID" - end.should raise_error(Puppet::Error) + it "should have :name be its namevar" do + @class.key_attributes.should == [:name] end - it "should accept empty environment variables that do not contain '='" do - lambda do - @cron[:environment] = "MAILTO=" - end.should_not raise_error(Puppet::Error) + describe "when validating attributes" do + + [:name, :provider].each do |param| + it "should have a #{param} parameter" do + @class.attrtype(param).should == :param + end + end + + [:command, :special, :minute, :hour, :weekday, :month, :monthday, :environment, :user, :target].each do |property| + it "should have a #{property} property" do + @class.attrtype(property).should == :property + end + end + + [:command, :minute, :hour, :weekday, :month, :monthday].each do |cronparam| + it "should have #{cronparam} of type CronParam" do + @class.attrclass(cronparam).ancestors.should include CronParam + end + end + end - it "should accept 'absent'" do - lambda do - @cron[:environment] = 'absent' - end.should_not raise_error(Puppet::Error) + + describe "when validating attribute" do + + describe "ensure" do + it "should support present as a value for ensure" do + proc { @class.new(:name => 'foo', :ensure => :present) }.should_not raise_error + end + + it "should support absent as a value for ensure" do + proc { @class.new(:name => 'foo', :ensure => :present) }.should_not raise_error + end + end + + describe "minute" do + + it "should support absent" do + proc { @class.new(:name => 'foo', :minute => 'absent') }.should_not raise_error + end + + it "should support *" do + proc { @class.new(:name => 'foo', :minute => '*') }.should_not raise_error + end + + it "should translate absent to :absent" do + @class.new(:name => 'foo', :minute => 'absent')[:minute].should == :absent + end + + it "should translate * to :absent" do + @class.new(:name => 'foo', :minute => '*')[:minute].should == :absent + end + + it "should support valid single values" do + proc { @class.new(:name => 'foo', :minute => '0') }.should_not raise_error + proc { @class.new(:name => 'foo', :minute => '1') }.should_not raise_error + proc { @class.new(:name => 'foo', :minute => '59') }.should_not raise_error + end + + it "should not support non numeric characters" do + proc { @class.new(:name => 'foo', :minute => 'z59') }.should raise_error(Puppet::Error) + proc { @class.new(:name => 'foo', :minute => '5z9') }.should raise_error(Puppet::Error) + proc { @class.new(:name => 'foo', :minute => '59z') }.should raise_error(Puppet::Error) + end + + it "should not support single values out of range" do + + proc { @class.new(:name => 'foo', :minute => '-1') }.should raise_error(Puppet::Error) + proc { @class.new(:name => 'foo', :minute => '60') }.should raise_error(Puppet::Error) + proc { @class.new(:name => 'foo', :minute => '61') }.should raise_error(Puppet::Error) + proc { @class.new(:name => 'foo', :minute => '120') }.should raise_error(Puppet::Error) + end + + it "should support valid multiple values" do + proc { @class.new(:name => 'foo', :minute => ['0','1','59'] ) }.should_not raise_error + proc { @class.new(:name => 'foo', :minute => ['40','30','20'] ) }.should_not raise_error + proc { @class.new(:name => 'foo', :minute => ['10','30','20'] ) }.should_not raise_error + end + + it "should not support multiple values if at least one is invalid" do + # one invalid + proc { @class.new(:name => 'foo', :minute => ['0','1','60'] ) }.should raise_error(Puppet::Error) + proc { @class.new(:name => 'foo', :minute => ['0','120','59'] ) }.should raise_error(Puppet::Error) + proc { @class.new(:name => 'foo', :minute => ['-1','1','59'] ) }.should raise_error(Puppet::Error) + # two invalid + proc { @class.new(:name => 'foo', :minute => ['0','61','62'] ) }.should raise_error(Puppet::Error) + # all invalid + proc { @class.new(:name => 'foo', :minute => ['-1','61','62'] ) }.should raise_error(Puppet::Error) + end + + it "should support valid step syntax" do + proc { @class.new(:name => 'foo', :minute => '*/2' ) }.should_not raise_error + proc { @class.new(:name => 'foo', :minute => '10-16/2' ) }.should_not raise_error + end + + it "should not support invalid steps" do + proc { @class.new(:name => 'foo', :minute => '*/A' ) }.should raise_error(Puppet::Error) + proc { @class.new(:name => 'foo', :minute => '*/2A' ) }.should raise_error(Puppet::Error) + # As it turns out cron does not complaining about steps that exceed the valid range + # proc { @class.new(:name => 'foo', :minute => '*/120' ) }.should raise_error(Puppet::Error) + end + + end + + describe "hour" do + + it "should support absent" do + proc { @class.new(:name => 'foo', :hour => 'absent') }.should_not raise_error + end + + it "should support *" do + proc { @class.new(:name => 'foo', :hour => '*') }.should_not raise_error + end + + it "should translate absent to :absent" do + @class.new(:name => 'foo', :hour => 'absent')[:hour].should == :absent + end + + it "should translate * to :absent" do + @class.new(:name => 'foo', :hour => '*')[:hour].should == :absent + end + + it "should support valid single values" do + proc { @class.new(:name => 'foo', :hour => '0') }.should_not raise_error + proc { @class.new(:name => 'foo', :hour => '11') }.should_not raise_error + proc { @class.new(:name => 'foo', :hour => '12') }.should_not raise_error + proc { @class.new(:name => 'foo', :hour => '13') }.should_not raise_error + proc { @class.new(:name => 'foo', :hour => '23') }.should_not raise_error + end + + it "should not support non numeric characters" do + proc { @class.new(:name => 'foo', :hour => 'z15') }.should raise_error(Puppet::Error) + proc { @class.new(:name => 'foo', :hour => '1z5') }.should raise_error(Puppet::Error) + proc { @class.new(:name => 'foo', :hour => '15z') }.should raise_error(Puppet::Error) + end + + it "should not support single values out of range" do + proc { @class.new(:name => 'foo', :hour => '-1') }.should raise_error(Puppet::Error) + proc { @class.new(:name => 'foo', :hour => '24') }.should raise_error(Puppet::Error) + proc { @class.new(:name => 'foo', :hour => '120') }.should raise_error(Puppet::Error) + end + + it "should support valid multiple values" do + proc { @class.new(:name => 'foo', :hour => ['0','1','23'] ) }.should_not raise_error + proc { @class.new(:name => 'foo', :hour => ['5','16','14'] ) }.should_not raise_error + proc { @class.new(:name => 'foo', :hour => ['16','13','9'] ) }.should_not raise_error + end + + it "should not support multiple values if at least one is invalid" do + # one invalid + proc { @class.new(:name => 'foo', :hour => ['0','1','24'] ) }.should raise_error(Puppet::Error) + proc { @class.new(:name => 'foo', :hour => ['0','-1','5'] ) }.should raise_error(Puppet::Error) + proc { @class.new(:name => 'foo', :hour => ['-1','1','23'] ) }.should raise_error(Puppet::Error) + # two invalid + proc { @class.new(:name => 'foo', :hour => ['0','25','26'] ) }.should raise_error(Puppet::Error) + # all invalid + proc { @class.new(:name => 'foo', :hour => ['-1','24','120'] ) }.should raise_error(Puppet::Error) + end + + it "should support valid step syntax" do + proc { @class.new(:name => 'foo', :hour => '*/2' ) }.should_not raise_error + proc { @class.new(:name => 'foo', :hour => '10-18/4' ) }.should_not raise_error + end + + it "should not support invalid steps" do + proc { @class.new(:name => 'foo', :hour => '*/A' ) }.should raise_error(Puppet::Error) + proc { @class.new(:name => 'foo', :hour => '*/2A' ) }.should raise_error(Puppet::Error) + # As it turns out cron does not complaining about steps that exceed the valid range + # proc { @class.new(:name => 'foo', :hour => '*/26' ) }.should raise_error(Puppet::Error) + end + + end + + describe "weekday" do + + it "should support absent" do + proc { @class.new(:name => 'foo', :weekday => 'absent') }.should_not raise_error + end + + it "should support *" do + proc { @class.new(:name => 'foo', :weekday => '*') }.should_not raise_error + end + + it "should translate absent to :absent" do + @class.new(:name => 'foo', :weekday => 'absent')[:weekday].should == :absent + end + + it "should translate * to :absent" do + @class.new(:name => 'foo', :weekday => '*')[:weekday].should == :absent + end + + it "should support valid numeric weekdays" do + proc { @class.new(:name => 'foo', :weekday => '0') }.should_not raise_error + proc { @class.new(:name => 'foo', :weekday => '1') }.should_not raise_error + proc { @class.new(:name => 'foo', :weekday => '6') }.should_not raise_error + # According to http://www.manpagez.com/man/5/crontab 7 is also valid (Sunday) + proc { @class.new(:name => 'foo', :weekday => '7') }.should_not raise_error + end + + it "should support valid weekdays as words (3 character version)" do + proc { @class.new(:name => 'foo', :weekday => 'Monday') }.should_not raise_error + proc { @class.new(:name => 'foo', :weekday => 'Tuesday') }.should_not raise_error + proc { @class.new(:name => 'foo', :weekday => 'Wednesday') }.should_not raise_error + proc { @class.new(:name => 'foo', :weekday => 'Thursday') }.should_not raise_error + proc { @class.new(:name => 'foo', :weekday => 'Friday') }.should_not raise_error + proc { @class.new(:name => 'foo', :weekday => 'Saturday') }.should_not raise_error + proc { @class.new(:name => 'foo', :weekday => 'Sunday') }.should_not raise_error + end + + it "should support valid weekdays as words (3 character version)" do + proc { @class.new(:name => 'foo', :weekday => 'Mon') }.should_not raise_error + proc { @class.new(:name => 'foo', :weekday => 'Tue') }.should_not raise_error + proc { @class.new(:name => 'foo', :weekday => 'Wed') }.should_not raise_error + proc { @class.new(:name => 'foo', :weekday => 'Thu') }.should_not raise_error + proc { @class.new(:name => 'foo', :weekday => 'Fri') }.should_not raise_error + proc { @class.new(:name => 'foo', :weekday => 'Sat') }.should_not raise_error + proc { @class.new(:name => 'foo', :weekday => 'Sun') }.should_not raise_error + end + + it "should not support numeric values out of range" do + proc { @class.new(:name => 'foo', :weekday => '-1') }.should raise_error(Puppet::Error) + proc { @class.new(:name => 'foo', :weekday => '8') }.should raise_error(Puppet::Error) + end + + it "should not support invalid weekday names" do + proc { @class.new(:name => 'foo', :weekday => 'Sar') }.should raise_error(Puppet::Error) + end + + it "should support valid multiple values" do + proc { @class.new(:name => 'foo', :weekday => ['0','1','6'] ) }.should_not raise_error + proc { @class.new(:name => 'foo', :weekday => ['Mon','Wed','Friday'] ) }.should_not raise_error + end + + it "should not support multiple values if at least one is invalid" do + # one invalid + proc { @class.new(:name => 'foo', :weekday => ['0','1','8'] ) }.should raise_error(Puppet::Error) + proc { @class.new(:name => 'foo', :weekday => ['Mon','Fii','Sat'] ) }.should raise_error(Puppet::Error) + # two invalid + proc { @class.new(:name => 'foo', :weekday => ['Mos','Fii','Sat'] ) }.should raise_error(Puppet::Error) + # all invalid + proc { @class.new(:name => 'foo', :weekday => ['Mos','Fii','Saa'] ) }.should raise_error(Puppet::Error) + proc { @class.new(:name => 'foo', :weekday => ['-1','8','11'] ) }.should raise_error(Puppet::Error) + end + + it "should support valid step syntax" do + proc { @class.new(:name => 'foo', :weekday => '*/2' ) }.should_not raise_error + proc { @class.new(:name => 'foo', :weekday => '0-4/2' ) }.should_not raise_error + end + + it "should not support invalid steps" do + proc { @class.new(:name => 'foo', :weekday => '*/A' ) }.should raise_error(Puppet::Error) + proc { @class.new(:name => 'foo', :weekday => '*/2A' ) }.should raise_error(Puppet::Error) + # As it turns out cron does not complaining about steps that exceed the valid range + # proc { @class.new(:name => 'foo', :weekday => '*/9' ) }.should raise_error(Puppet::Error) + end + + end + + describe "month" do + + it "should support absent" do + proc { @class.new(:name => 'foo', :month => 'absent') }.should_not raise_error + end + + it "should support *" do + proc { @class.new(:name => 'foo', :month => '*') }.should_not raise_error + end + + it "should translate absent to :absent" do + @class.new(:name => 'foo', :month => 'absent')[:month].should == :absent + end + + it "should translate * to :absent" do + @class.new(:name => 'foo', :month => '*')[:month].should == :absent + end + + it "should support valid numeric values" do + proc { @class.new(:name => 'foo', :month => '1') }.should_not raise_error + proc { @class.new(:name => 'foo', :month => '12') }.should_not raise_error + end + + it "should support valid months as words" do + proc { @class.new(:name => 'foo', :month => 'January') }.should_not raise_error + proc { @class.new(:name => 'foo', :month => 'February') }.should_not raise_error + proc { @class.new(:name => 'foo', :month => 'March') }.should_not raise_error + proc { @class.new(:name => 'foo', :month => 'April') }.should_not raise_error + proc { @class.new(:name => 'foo', :month => 'May') }.should_not raise_error + proc { @class.new(:name => 'foo', :month => 'June') }.should_not raise_error + proc { @class.new(:name => 'foo', :month => 'July') }.should_not raise_error + proc { @class.new(:name => 'foo', :month => 'August') }.should_not raise_error + proc { @class.new(:name => 'foo', :month => 'September') }.should_not raise_error + proc { @class.new(:name => 'foo', :month => 'October') }.should_not raise_error + proc { @class.new(:name => 'foo', :month => 'November') }.should_not raise_error + proc { @class.new(:name => 'foo', :month => 'December') }.should_not raise_error + end + + it "should support valid months as words (3 character short version)" do + proc { @class.new(:name => 'foo', :month => 'Jan') }.should_not raise_error + proc { @class.new(:name => 'foo', :month => 'Feb') }.should_not raise_error + proc { @class.new(:name => 'foo', :month => 'Mar') }.should_not raise_error + proc { @class.new(:name => 'foo', :month => 'Apr') }.should_not raise_error + proc { @class.new(:name => 'foo', :month => 'May') }.should_not raise_error + proc { @class.new(:name => 'foo', :month => 'Jun') }.should_not raise_error + proc { @class.new(:name => 'foo', :month => 'Jul') }.should_not raise_error + proc { @class.new(:name => 'foo', :month => 'Aug') }.should_not raise_error + proc { @class.new(:name => 'foo', :month => 'Sep') }.should_not raise_error + proc { @class.new(:name => 'foo', :month => 'Oct') }.should_not raise_error + proc { @class.new(:name => 'foo', :month => 'Nov') }.should_not raise_error + proc { @class.new(:name => 'foo', :month => 'Dec') }.should_not raise_error + end + + it "should not support numeric values out of range" do + proc { @class.new(:name => 'foo', :month => '-1') }.should raise_error(Puppet::Error) + proc { @class.new(:name => 'foo', :month => '0') }.should raise_error(Puppet::Error) + proc { @class.new(:name => 'foo', :month => '13') }.should raise_error(Puppet::Error) + end + + it "should not support words that are not valid months" do + proc { @class.new(:name => 'foo', :month => 'Jal') }.should raise_error(Puppet::Error) + end + + it "should not support single values out of range" do + + proc { @class.new(:name => 'foo', :month => '-1') }.should raise_error(Puppet::Error) + proc { @class.new(:name => 'foo', :month => '60') }.should raise_error(Puppet::Error) + proc { @class.new(:name => 'foo', :month => '61') }.should raise_error(Puppet::Error) + proc { @class.new(:name => 'foo', :month => '120') }.should raise_error(Puppet::Error) + end + + it "should support valid multiple values" do + proc { @class.new(:name => 'foo', :month => ['1','9','12'] ) }.should_not raise_error + proc { @class.new(:name => 'foo', :month => ['Jan','March','Jul'] ) }.should_not raise_error + end + + it "should not support multiple values if at least one is invalid" do + # one invalid + proc { @class.new(:name => 'foo', :month => ['0','1','12'] ) }.should raise_error(Puppet::Error) + proc { @class.new(:name => 'foo', :month => ['1','13','10'] ) }.should raise_error(Puppet::Error) + proc { @class.new(:name => 'foo', :month => ['Jan','Feb','Jxx'] ) }.should raise_error(Puppet::Error) + # two invalid + proc { @class.new(:name => 'foo', :month => ['Jan','Fex','Jux'] ) }.should raise_error(Puppet::Error) + # all invalid + proc { @class.new(:name => 'foo', :month => ['-1','0','13'] ) }.should raise_error(Puppet::Error) + proc { @class.new(:name => 'foo', :month => ['Jax','Fex','Aux'] ) }.should raise_error(Puppet::Error) + end + + it "should support valid step syntax" do + proc { @class.new(:name => 'foo', :month => '*/2' ) }.should_not raise_error + proc { @class.new(:name => 'foo', :month => '1-12/3' ) }.should_not raise_error + end + + it "should not support invalid steps" do + proc { @class.new(:name => 'foo', :month => '*/A' ) }.should raise_error(Puppet::Error) + proc { @class.new(:name => 'foo', :month => '*/2A' ) }.should raise_error(Puppet::Error) + # As it turns out cron does not complaining about steps that exceed the valid range + # proc { @class.new(:name => 'foo', :month => '*/13' ) }.should raise_error(Puppet::Error) + end + + end + + describe "monthday" do + + it "should support absent" do + proc { @class.new(:name => 'foo', :monthday => 'absent') }.should_not raise_error + end + + it "should support *" do + proc { @class.new(:name => 'foo', :monthday => '*') }.should_not raise_error + end + + it "should translate absent to :absent" do + @class.new(:name => 'foo', :monthday => 'absent')[:monthday].should == :absent + end + + it "should translate * to :absent" do + @class.new(:name => 'foo', :monthday => '*')[:monthday].should == :absent + end + + it "should support valid single values" do + proc { @class.new(:name => 'foo', :monthday => '1') }.should_not raise_error + proc { @class.new(:name => 'foo', :monthday => '30') }.should_not raise_error + proc { @class.new(:name => 'foo', :monthday => '31') }.should_not raise_error + end + + it "should not support non numeric characters" do + proc { @class.new(:name => 'foo', :monthday => 'z23') }.should raise_error(Puppet::Error) + proc { @class.new(:name => 'foo', :monthday => '2z3') }.should raise_error(Puppet::Error) + proc { @class.new(:name => 'foo', :monthday => '23z') }.should raise_error(Puppet::Error) + end + + it "should not support single values out of range" do + proc { @class.new(:name => 'foo', :monthday => '-1') }.should raise_error(Puppet::Error) + proc { @class.new(:name => 'foo', :monthday => '0') }.should raise_error(Puppet::Error) + proc { @class.new(:name => 'foo', :monthday => '32') }.should raise_error(Puppet::Error) + end + + it "should support valid multiple values" do + proc { @class.new(:name => 'foo', :monthday => ['1','23','31'] ) }.should_not raise_error + proc { @class.new(:name => 'foo', :monthday => ['31','23','1'] ) }.should_not raise_error + proc { @class.new(:name => 'foo', :monthday => ['1','31','23'] ) }.should_not raise_error + end + + it "should not support multiple values if at least one is invalid" do + # one invalid + proc { @class.new(:name => 'foo', :monthday => ['1','23','32'] ) }.should raise_error(Puppet::Error) + proc { @class.new(:name => 'foo', :monthday => ['-1','12','23'] ) }.should raise_error(Puppet::Error) + proc { @class.new(:name => 'foo', :monthday => ['13','32','30'] ) }.should raise_error(Puppet::Error) + # two invalid + proc { @class.new(:name => 'foo', :monthday => ['-1','0','23'] ) }.should raise_error(Puppet::Error) + # all invalid + proc { @class.new(:name => 'foo', :monthday => ['-1','0','32'] ) }.should raise_error(Puppet::Error) + end + + it "should support valid step syntax" do + proc { @class.new(:name => 'foo', :monthday => '*/2' ) }.should_not raise_error + proc { @class.new(:name => 'foo', :monthday => '10-16/2' ) }.should_not raise_error + end + + it "should not support invalid steps" do + proc { @class.new(:name => 'foo', :monthday => '*/A' ) }.should raise_error(Puppet::Error) + proc { @class.new(:name => 'foo', :monthday => '*/2A' ) }.should raise_error(Puppet::Error) + # As it turns out cron does not complaining about steps that exceed the valid range + # proc { @class.new(:name => 'foo', :monthday => '*/32' ) }.should raise_error(Puppet::Error) + end + + end + + describe "environment" do + + it "it should accept an :environment that looks like a path" do + lambda do + @cron[:environment] = 'PATH=/bin:/usr/bin:/usr/sbin' + end.should_not raise_error + end + + it "should not accept environment variables that do not contain '='" do + lambda do + @cron[:environment] = "INVALID" + end.should raise_error(Puppet::Error) + end + + it "should accept empty environment variables that do not contain '='" do + lambda do + @cron[:environment] = "MAILTO=" + end.should_not raise_error(Puppet::Error) + end + + it "should accept 'absent'" do + lambda do + @cron[:environment] = 'absent' + end.should_not raise_error(Puppet::Error) + end + + end + end end diff --git a/spec/unit/type/exec_spec.rb b/spec/unit/type/exec_spec.rb index e04cfc065..e155506b0 100755 --- a/spec/unit/type/exec_spec.rb +++ b/spec/unit/type/exec_spec.rb @@ -1,162 +1,689 @@ #!/usr/bin/env ruby - require File.dirname(__FILE__) + '/../../spec_helper' describe Puppet::Type.type(:exec) do - - def create_resource(command, output, exitstatus, returns = 0) - @user_name = 'some_user_name' + def exec_tester(command, exitstatus = 0, rest = {}) + @user_name = 'some_user_name' @group_name = 'some_group_name' Puppet.features.stubs(:root?).returns(true) - @execer = Puppet::Type.type(:exec).new(:name => command, :path => @example_path, :user => @user_name, :group => @group_name, :returns => returns) - status = stub "process" - status.stubs(:exitstatus).returns(exitstatus) + output = rest.delete(:output) || '' + tries = rest[:tries] || 1 + + args = { + :name => command, + :path => @example_path, + :user => @user_name, + :group => @group_name, + :logoutput => false, + :loglevel => :err, + :returns => 0 + }.merge(rest) + + exec = Puppet::Type.type(:exec).new(args) - Puppet::Util::SUIDManager.expects(:run_and_capture).with([command], @user_name, @group_name).returns([output, status]) + status = stub "process", :exitstatus => exitstatus + Puppet::Util::SUIDManager.expects(:run_and_capture).times(tries). + with([command], @user_name, @group_name).returns([output, status]) + + return exec end - def create_logging_resource(command, output, exitstatus, logoutput, loglevel, returns = 0) - create_resource(command, output, exitstatus, returns) - @execer[:logoutput] = logoutput - @execer[:loglevel] = loglevel + before do + @command = Puppet.features.posix? ? '/bin/true whatever' : '"C:/Program Files/something.exe" whatever' end - def expect_output(output, loglevel) - output.split(/\n/).each do |line| - @execer.property(:returns).expects(loglevel).with(line) + describe "when not stubbing the provider" do + before do + @executable = Puppet.features.posix? ? '/bin/true' : 'C:/Program Files/something.exe' + File.stubs(:exists?).returns false + File.stubs(:exists?).with(@executable).returns true + File.stubs(:exists?).with('/bin/false').returns true + @example_path = Puppet.features.posix? ? %w{/usr/bin /bin} : [ "C:/Program Files/something/bin", "C:/Ruby/bin" ] + File.stubs(:exists?).with(File.join(@example_path[0],"true")).returns true + File.stubs(:exists?).with(File.join(@example_path[0],"false")).returns true + end + + it "should return :executed_command as its event" do + resource = Puppet::Type.type(:exec).new :command => @command + resource.parameter(:returns).event.name.should == :executed_command + end + + describe "when execing" do + it "should use the 'run_and_capture' method to exec" do + exec_tester("true").refresh.should == :executed_command + end + + it "should report a failure" do + proc { exec_tester('false', 1).refresh }. + should raise_error(Puppet::Error, /^false returned 1 instead of/) + end + + it "should not report a failure if the exit status is specified in a returns array" do + proc { exec_tester("false", 1, :returns => [0, 1]).refresh }.should_not raise_error + end + + it "should report a failure if the exit status is not specified in a returns array" do + proc { exec_tester('false', 1, :returns => [0, 100]).refresh }. + should raise_error(Puppet::Error, /^false returned 1 instead of/) + end + + it "should log the output on success" do + output = "output1\noutput2\n" + exec_tester('false', 0, :output => output, :logoutput => true).refresh + output.split("\n").each do |line| + log = @logs.shift + log.level.should == :err + log.message.should == line + end + end + + it "should log the output on failure" do + output = "output1\noutput2\n" + proc { exec_tester('false', 1, :output => output, :logoutput => true).refresh }. + should raise_error(Puppet::Error) + + output.split("\n").each do |line| + log = @logs.shift + log.level.should == :err + log.message.should == line + end + end + end + + describe "when logoutput=>on_failure is set" do + it "should log the output on failure" do + output = "output1\noutput2\n" + proc { exec_tester('false', 1, :output => output, :logoutput => :on_failure).refresh }. + should raise_error(Puppet::Error, /^false returned 1 instead of/) + + output.split("\n").each do |line| + log = @logs.shift + log.level.should == :err + log.message.should == line + end + end + + it "should log the output on failure when returns is specified as an array" do + output = "output1\noutput2\n" + + proc { + exec_tester('false', 1, :output => output, :returns => [0, 100], + :logoutput => :on_failure).refresh + }.should raise_error(Puppet::Error, /^false returned 1 instead of/) + + output.split("\n").each do |line| + log = @logs.shift + log.level.should == :err + log.message.should == line + end + end + + it "shouldn't log the output on success" do + exec_tester('true', 0, :output => "a\nb\nc\n", :logoutput => :on_failure).refresh + @logs.should == [] + end + end + + it "shouldn't log the output on success when non-zero exit status is in a returns array" do + exec_tester("true", 100, :output => "a\n", :logoutput => :on_failure, :returns => [1, 100]).refresh + @logs.should == [] + end + + describe " when multiple tries are set," do + it "should repeat the command attempt 'tries' times on failure and produce an error" do + tries = 5 + resource = exec_tester("false", 1, :tries => tries, :try_sleep => 0) + proc { resource.refresh }.should raise_error(Puppet::Error) + end end end - before do - @executable = Puppet.features.posix? ? '/bin/true' : 'C:/Program Files/something.exe' - @command = Puppet.features.posix? ? '/bin/true whatever' : '"C:/Program Files/something.exe" whatever' - File.stubs(:exists?).returns false - File.stubs(:exists?).with(@executable).returns true - @example_path = Puppet.features.posix? ? %w{/usr/bin /bin} : [ "C:/Program Files/something/bin", "C:/Ruby/bin" ] - File.stubs(:exists?).with(File.join(@example_path[0],"true")).returns true - File.stubs(:exists?).with(File.join(@example_path[0],"false")).returns true + it "should be able to autorequire files mentioned in the command" do + catalog = Puppet::Resource::Catalog.new + tmp = Puppet::Type.type(:file).new(:name => "/bin/foo") + catalog.add_resource tmp + execer = Puppet::Type.type(:exec).new(:name => "/bin/foo") + catalog.add_resource execer + + catalog.relationship_graph.dependencies(execer).should == [tmp] end - it "should return :executed_command as its event" do - resource = Puppet::Type.type(:exec).new :command => @command - resource.parameter(:returns).event.name.should == :executed_command + describe "when handling the path parameter" do + expect = %w{one two three four} + { "an array" => expect, + "a colon separated list" => "one:two:three:four", + "a semi-colon separated list" => "one;two;three;four", + "both array and colon lists" => ["one", "two:three", "four"], + "both array and semi-colon lists" => ["one", "two;three", "four"], + "colon and semi-colon lists" => ["one:two", "three;four"] + }.each do |test, input| + it "should accept #{test}" do + type = Puppet::Type.type(:exec).new(:name => @command, :path => input) + type[:path].should == expect + end + end end - describe "when execing" do + describe "when setting user" do + it "should fail if we are not root" do + Puppet.features.stubs(:root?).returns(false) + expect { Puppet::Type.type(:exec).new(:name => @command, :user => 'input') }. + should raise_error Puppet::Error, /Parameter user failed/ + end - it "should use the 'run_and_capture' method to exec" do - command = "true" - create_resource(command, "", 0) + ['one', 2, 'root', 4294967295, 4294967296].each do |value| + it "should accept '#{value}' as user if we are root" do + Puppet.features.stubs(:root?).returns(true) + type = Puppet::Type.type(:exec).new(:name => @command, :user => value) + type[:user].should == value + end + end + end - @execer.refresh.should == :executed_command + describe "when setting group" do + shared_examples_for "exec[:group]" do + ['one', 2, 'wheel', 4294967295, 4294967296].each do |value| + it "should accept '#{value}' without error or judgement" do + type = Puppet::Type.type(:exec).new(:name => @command, :group => value) + type[:group].should == value + end + end end - it "should report a failure" do - command = "false" - create_resource(command, "", 1) + describe "when running as root" do + before :each do Puppet.features.stubs(:root?).returns(true) end + it_behaves_like "exec[:group]" + end + + describe "when not running as root" do + before :each do Puppet.features.stubs(:root?).returns(false) end + it_behaves_like "exec[:group]" + end + end + + describe "when setting cwd" do + it_should_behave_like "all path parameters", :cwd, :array => false do + def instance(path) + Puppet::Type.type(:exec).new(:name => '/bin/true', :cwd => path) + end + end + end + + shared_examples_for "all exec command parameters" do |param| + { "relative" => "example", "absolute" => "/bin/example" }.sort.each do |name, command| + describe "if command is #{name}" do + before :each do + @param = param + end - proc { @execer.refresh }.should raise_error(Puppet::Error) + def test(command, valid) + if @param == :name then + instance = Puppet::Type.type(:exec).new() + else + instance = Puppet::Type.type(:exec).new(:name => "/bin/true") + end + if valid then + instance.provider.expects(:validatecmd).returns(true) + else + instance.provider.expects(:validatecmd).raises(Puppet::Error, "from a stub") + end + instance[@param] = command + end + + it "should work if the provider calls the command valid" do + expect { test(command, true) }.should_not raise_error + end + + it "should fail if the provider calls the command invalid" do + expect { test(command, false) }. + should raise_error Puppet::Error, /Parameter #{@param} failed: from a stub/ + end + end end + end + + shared_examples_for "all exec command parameters that take arrays" do |param| + describe "when given an array of inputs" do + before :each do + @test = Puppet::Type.type(:exec).new(:name => "/bin/true") + end - it "should not report a failure if the exit status is specified in a returns array" do - command = "false" - create_resource(command, "", 1, [0,1]) - proc { @execer.refresh }.should_not raise_error(Puppet::Error) + it "should accept the array when all commands return valid" do + input = %w{one two three} + @test.provider.expects(:validatecmd).times(input.length).returns(true) + @test[param] = input + @test[param].should == input + end + + it "should reject the array when any commands return invalid" do + input = %w{one two three} + @test.provider.expects(:validatecmd).with(input.first).returns(false) + input[1..-1].each do |cmd| + @test.provider.expects(:validatecmd).with(cmd).returns(true) + end + @test[param] = input + @test[param].should == input + end + + it "should reject the array when all commands return invalid" do + input = %w{one two three} + @test.provider.expects(:validatecmd).times(input.length).returns(false) + @test[param] = input + @test[param].should == input + end end + end - it "should report a failure if the exit status is not specified in a returns array" do - command = "false" - create_resource(command, "", 1, [0,100]) - proc { @execer.refresh }.should raise_error(Puppet::Error) + describe "when setting refresh" do + it_should_behave_like "all exec command parameters", :refresh + end + + describe "for simple parameters" do + before :each do + @exec = Puppet::Type.type(:exec).new(:name => '/bin/true') end - it "should log the output on success" do - #Puppet::Util::Log.newdestination :console - command = "false" - output = "output1\noutput2\n" - create_logging_resource(command, output, 0, true, :err) - expect_output(output, :err) - @execer.refresh + describe "when setting env" do + it "should issue a deprecation warning" do + expect { @exec[:env] = 'foo=bar' }.should_not raise_error + @logs.first.message.should =~ /deprecate.*environment/ + end + + it "should update the value of env" do + data = ['foo=bar'] + @exec[:env] = data + @exec[:env].should == data + end + + it "should forward to environment" do + data = ['foo=bar'] + @exec[:env] = data + @exec[:environment].should == data + end + + it "should not override environment if both are set" do + pending "can't fix: too disruptive for 2.6, removed in 2.7" + # ...so this test is here to validate that we know about the problem. + # This ensures correct order of evaluation to trigger the bug; don't + # count on this happening in the constructor. --daniel 2011-03-01 + @exec[:environment] = 'environment=true' + @exec[:env] = 'env=true' + + @exec[:environment].should == "environment=true" + end end - it "should log the output on failure" do - #Puppet::Util::Log.newdestination :console - command = "false" - output = "output1\noutput2\n" - create_logging_resource(command, output, 1, true, :err) - expect_output(output, :err) + describe "when setting environment" do + { "single values" => "foo=bar", + "multiple values" => ["foo=bar", "baz=quux"], + }.each do |name, data| + it "should accept #{name}" do + @exec[:environment] = data + @exec[:environment].should == data + end + end - proc { @execer.refresh }.should raise_error(Puppet::Error) + { "single values" => "foo", + "only values" => ["foo", "bar"], + "any values" => ["foo=bar", "baz"] + }.each do |name, data| + it "should reject #{name} without assignment" do + expect { @exec[:environment] = data }. + should raise_error Puppet::Error, /Invalid environment setting/ + end + end end + describe "when setting timeout" do + [0, 0.1, 1, 10, 4294967295].each do |valid| + it "should accept '#{valid}' as valid" do + @exec[:timeout] = valid + @exec[:timeout].should == valid + end + + it "should accept '#{valid}' in an array as valid" do + @exec[:timeout] = [valid] + @exec[:timeout].should == valid + end + end + + ['1/2', '', 'foo', '5foo'].each do |invalid| + it "should reject '#{invalid}' as invalid" do + expect { @exec[:timeout] = invalid }. + should raise_error Puppet::Error, /The timeout must be a number/ + end + + it "should reject '#{invalid}' in an array as invalid" do + expect { @exec[:timeout] = [invalid] }. + should raise_error Puppet::Error, /The timeout must be a number/ + end + end + + it "should fail if timeout is exceeded" do + File.stubs(:exists?).with('/bin/sleep').returns(true) + File.stubs(:exists?).with('sleep').returns(false) + sleep_exec = Puppet::Type.type(:exec).new(:name => 'sleep 1', :path => ['/bin'], :timeout => '0.2') + lambda { sleep_exec.refresh }.should raise_error Puppet::Error, "Command exceeded timeout" + end + + it "should convert timeout to a float" do + resource = Puppet::Type.type(:exec).new :command => "/bin/false", :timeout => "12" + resource[:timeout].should be_a(Float) + resource[:timeout].should == 12.0 + end + + it "should munge negative timeouts to 0.0" do + resource = Puppet::Type.type(:exec).new :command => "/bin/false", :timeout => "-12.0" + resource.parameter(:timeout).value.should be_a(Float) + resource.parameter(:timeout).value.should == 0.0 + end + end + + describe "when setting tries" do + [1, 10, 4294967295].each do |valid| + it "should accept '#{valid}' as valid" do + @exec[:tries] = valid + @exec[:tries].should == valid + end + + if "REVISIT: too much test log spam" == "a good thing" then + it "should accept '#{valid}' in an array as valid" do + pending "inconsistent, but this is not supporting arrays, unlike timeout" + @exec[:tries] = [valid] + @exec[:tries].should == valid + end + end + end + + [-3.5, -1, 0, 0.2, '1/2', '1_000_000', '+12', '', 'foo'].each do |invalid| + it "should reject '#{invalid}' as invalid" do + expect { @exec[:tries] = invalid }. + should raise_error Puppet::Error, /Tries must be an integer/ + end + + if "REVISIT: too much test log spam" == "a good thing" then + it "should reject '#{invalid}' in an array as invalid" do + pending "inconsistent, but this is not supporting arrays, unlike timeout" + expect { @exec[:tries] = [invalid] }. + should raise_error Puppet::Error, /Tries must be an integer/ + end + end + end + end + + describe "when setting try_sleep" do + [0, 0.2, 1, 10, 4294967295].each do |valid| + it "should accept '#{valid}' as valid" do + @exec[:try_sleep] = valid + @exec[:try_sleep].should == valid + end + + if "REVISIT: too much test log spam" == "a good thing" then + it "should accept '#{valid}' in an array as valid" do + pending "inconsistent, but this is not supporting arrays, unlike timeout" + @exec[:try_sleep] = [valid] + @exec[:try_sleep].should == valid + end + end + end + + { -3.5 => "cannot be a negative number", + -1 => "cannot be a negative number", + '1/2' => 'must be a number', + '1_000_000' => 'must be a number', + '+12' => 'must be a number', + '' => 'must be a number', + 'foo' => 'must be a number', + }.each do |invalid, error| + it "should reject '#{invalid}' as invalid" do + expect { @exec[:try_sleep] = invalid }. + should raise_error Puppet::Error, /try_sleep #{error}/ + end + + if "REVISIT: too much test log spam" == "a good thing" then + it "should reject '#{invalid}' in an array as invalid" do + pending "inconsistent, but this is not supporting arrays, unlike timeout" + expect { @exec[:try_sleep] = [invalid] }. + should raise_error Puppet::Error, /try_sleep #{error}/ + end + end + end + end + + describe "when setting refreshonly" do + [:true, :false].each do |value| + it "should accept '#{value}'" do + @exec[:refreshonly] = value + @exec[:refreshonly].should == value + end + end + + [1, 0, "1", "0", "yes", "y", "no", "n"].each do |value| + it "should reject '#{value}'" do + expect { @exec[:refreshonly] = value }. + should raise_error(Puppet::Error, + /Invalid value #{value.inspect}\. Valid values are true, false/ + ) + end + end + end + + describe "when setting creates" do + it_should_behave_like "all path parameters", :creates, :array => true do + def instance(path) + Puppet::Type.type(:exec).new(:name => '/bin/true', :creates => path) + end + end + end end - describe "when logoutput=>on_failure is set" do + describe "when setting unless" do + it_should_behave_like "all exec command parameters", :unless + it_should_behave_like "all exec command parameters that take arrays", :unless + end - it "should log the output on failure" do - #Puppet::Util::Log.newdestination :console - command = "false" - output = "output1\noutput2\n" - create_logging_resource(command, output, 1, :on_failure, :err) - expect_output(output, :err) + describe "when setting onlyif" do + it_should_behave_like "all exec command parameters", :onlyif + it_should_behave_like "all exec command parameters that take arrays", :onlyif + end - proc { @execer.refresh }.should raise_error(Puppet::Error) + describe "#check" do + before :each do + @test = Puppet::Type.type(:exec).new(:name => "/bin/true") end - it "should log the output on failure when returns is specified as an array" do - #Puppet::Util::Log.newdestination :console - command = "false" - output = "output1\noutput2\n" - create_logging_resource(command, output, 1, :on_failure, :err, [0, 100]) - expect_output(output, :err) + describe ":refreshonly" do + { :true => false, :false => true }.each do |input, result| + it "should return '#{result}' when given '#{input}'" do + @test[:refreshonly] = input + @test.check_all_attributes.should == result + end + end + end + + describe ":creates" do + before :all do + @exist = "/" + @unexist = "/this/path/should/never/exist" + while FileTest.exist?(@unexist) do @unexist += "/foo" end + end + + context "with a single item" do + it "should run when the item does not exist" do + @test[:creates] = @unexist + @test.check_all_attributes.should == true + end + + it "should not run when the item exists" do + @test[:creates] = @exist + @test.check_all_attributes.should == false + end + end + + context "with an array with one item" do + it "should run when the item does not exist" do + @test[:creates] = [@unexist] + @test.check_all_attributes.should == true + end + + it "should not run when the item exists" do + @test[:creates] = [@exist] + @test.check_all_attributes.should == false + end + end + + context "with an array with multiple items" do + it "should run when all items do not exist" do + @test[:creates] = [@unexist] * 3 + @test.check_all_attributes.should == true + end + + it "should not run when one item exists" do + @test[:creates] = [@unexist, @exist, @unexist] + @test.check_all_attributes.should == false + end - proc { @execer.refresh }.should raise_error(Puppet::Error) + it "should not run when all items exist" do + @test[:creates] = [@exist] * 3 + end + end end - it "shouldn't log the output on success" do - #Puppet::Util::Log.newdestination :console - command = "true" - output = "output1\noutput2\n" - create_logging_resource(command, output, 0, :on_failure, :err) - @execer.property(:returns).expects(:err).never - @execer.refresh + { :onlyif => { :pass => false, :fail => true }, + :unless => { :pass => true, :fail => false }, + }.each do |param, sense| + describe ":#{param}" do + before :each do + @pass = "/magic/pass" + @fail = "/magic/fail" + + @pass_status = stub('status', :exitstatus => sense[:pass] ? 0 : 1) + @fail_status = stub('status', :exitstatus => sense[:fail] ? 0 : 1) + + @test.provider.stubs(:checkexe).returns(true) + [true, false].each do |check| + @test.provider.stubs(:run).with(@pass, check). + returns(['test output', @pass_status]) + @test.provider.stubs(:run).with(@fail, check). + returns(['test output', @fail_status]) + end + end + + context "with a single item" do + it "should run if the command exits non-zero" do + @test[param] = @fail + @test.check_all_attributes.should == true + end + + it "should not run if the command exits zero" do + @test[param] = @pass + @test.check_all_attributes.should == false + end + end + + context "with an array with a single item" do + it "should run if the command exits non-zero" do + @test[param] = [@fail] + @test.check_all_attributes.should == true + end + + it "should not run if the command exits zero" do + @test[param] = [@pass] + @test.check_all_attributes.should == false + end + end + + context "with an array with multiple items" do + it "should run if all the commands exits non-zero" do + @test[param] = [@fail] * 3 + @test.check_all_attributes.should == true + end + + it "should not run if one command exits zero" do + @test[param] = [@pass, @fail, @pass] + @test.check_all_attributes.should == false + end + + it "should not run if all command exits zero" do + @test[param] = [@pass] * 3 + @test.check_all_attributes.should == false + end + end + end end end - it "shouldn't log the output on success when non-zero exit status is in a returns array" do - #Puppet::Util::Log.newdestination :console - command = "true" - output = "output1\noutput2\n" - create_logging_resource(command, output, 100, :on_failure, :err, [1,100]) - @execer.property(:returns).expects(:err).never - @execer.refresh + describe "#retrieve" do + before :each do + @exec_resource = Puppet::Type.type(:exec).new(:name => "/bogus/cmd") + end + + it "should return :notrun when check_all_attributes returns true" do + @exec_resource.stubs(:check_all_attributes).returns true + @exec_resource.retrieve[:returns].should == :notrun + end + + it "should return default exit code 0 when check_all_attributes returns false" do + @exec_resource.stubs(:check_all_attributes).returns false + @exec_resource.retrieve[:returns].should == ['0'] + end + + it "should return the specified exit code when check_all_attributes returns false" do + @exec_resource.stubs(:check_all_attributes).returns false + @exec_resource[:returns] = 42 + @exec_resource.retrieve[:returns].should == ["42"] + end end - describe " when multiple tries are set," do + describe "#output" do + before :each do + @exec_resource = Puppet::Type.type(:exec).new(:name => "/bogus/cmd") + end + + it "should return the provider's run output" do + provider = stub 'provider' + status = stubs "process_status" + status.stubs(:exitstatus).returns("0") + provider.expects(:run).returns(["silly output", status]) + @exec_resource.stubs(:provider).returns(provider) - it "should repeat the command attempt 'tries' times on failure and produce an error" do - Puppet.features.stubs(:root?).returns(true) - command = "false" - user = "user" - group = "group" - tries = 5 - retry_exec = Puppet::Type.type(:exec).new(:name => command, :path => %w{/usr/bin /bin}, :user => user, :group => group, :returns => 0, :tries => tries, :try_sleep => 0) - status = stub "process" - status.stubs(:exitstatus).returns(1) - Puppet::Util::SUIDManager.expects(:run_and_capture).with([command], user, group).times(tries).returns(["", status]) - proc { retry_exec.refresh }.should raise_error(Puppet::Error) + @exec_resource.refresh + @exec_resource.output.should == 'silly output' end end - it "should be able to autorequire files mentioned in the command" do - catalog = Puppet::Resource::Catalog.new - catalog.add_resource Puppet::Type.type(:file).new(:name => @executable) - @execer = Puppet::Type.type(:exec).new(:name => @command) - catalog.add_resource @execer + describe "#refresh" do + before :each do + @exec_resource = Puppet::Type.type(:exec).new(:name => "/bogus/cmd") + end + + it "should call provider run with the refresh parameter if it is set" do + provider = stub 'provider' + @exec_resource.stubs(:provider).returns(provider) + @exec_resource.stubs(:[]).with(:refresh).returns('/myother/bogus/cmd') + provider.expects(:run).with('/myother/bogus/cmd') - rels = @execer.autorequire - rels[0].should be_instance_of(Puppet::Relationship) - rels[0].target.should equal(@execer) + @exec_resource.refresh + end + + it "should call provider run with the specified command if the refresh parameter is not set" do + provider = stub 'provider' + status = stubs "process_status" + status.stubs(:exitstatus).returns("0") + provider.expects(:run).with('/bogus/cmd').returns(["silly output", status]) + @exec_resource.stubs(:provider).returns(provider) + + @exec_resource.refresh + end + + it "should not run the provider if check_all_attributes is false" do + @exec_resource.stubs(:check_all_attributes).returns false + provider = stub 'provider' + provider.expects(:run).never + @exec_resource.stubs(:provider).returns(provider) + + @exec_resource.refresh + end end end diff --git a/spec/unit/type_spec.rb b/spec/unit/type_spec.rb index 6d9d0b234..9b1f20500 100755 --- a/spec/unit/type_spec.rb +++ b/spec/unit/type_spec.rb @@ -1,6 +1,6 @@ #!/usr/bin/env ruby -require File.dirname(__FILE__) + '/../spec_helper' +require File.expand_path(File.join(File.dirname(__FILE__), '/../spec_helper')) describe Puppet::Type do it "should include the Cacher module" do diff --git a/spec/unit/util/loadedfile_spec.rb b/spec/unit/util/loadedfile_spec.rb index 3bc26a421..c6fd625fc 100755 --- a/spec/unit/util/loadedfile_spec.rb +++ b/spec/unit/util/loadedfile_spec.rb @@ -6,6 +6,7 @@ require 'tempfile' require 'puppet/util/loadedfile' describe Puppet::Util::LoadedFile do + include PuppetSpec::Files before(:each) do @f = Tempfile.new('loadedfile_test') @f.puts "yayness" @@ -18,6 +19,12 @@ describe Puppet::Util::LoadedFile do @fake_now = Time.now + (2 * Puppet[:filetimeout]) end + it "should accept files that don't exist" do + nofile = tmpfile('testfile') + File.exists?(nofile).should == false + lambda{ Puppet::Util::LoadedFile.new(nofile) }.should_not raise_error + end + it "should recognize when the file has not changed" do # Use fake "now" so that we can be sure changed? actually checks, without sleeping # for Puppet[:filetimeout] seconds. diff --git a/spec/unit/util/rdoc/parser_spec.rb b/spec/unit/util/rdoc/parser_spec.rb index b4453ae86..6ae28b40a 100755 --- a/spec/unit/util/rdoc/parser_spec.rb +++ b/spec/unit/util/rdoc/parser_spec.rb @@ -20,8 +20,9 @@ describe RDoc::Parser do @parser.stubs(:scan_top_level) parser = stub 'parser' Puppet::Parser::Parser.stubs(:new).returns(parser) - parser.expects(:parse) + parser.expects(:parse).at_least_once parser.expects(:file=).with("module/manifests/init.pp") + parser.expects(:file=).with("/dev/null/manifests/site.pp") @parser.scan end diff --git a/tasks/rake/git_workflow.rake b/tasks/rake/git_workflow.rake index 980d2fbce..f2ae7ee69 100644 --- a/tasks/rake/git_workflow.rake +++ b/tasks/rake/git_workflow.rake @@ -108,12 +108,14 @@ task :mail_patches do # If we've got more than one patch, add --compose if Dir.glob("00*.patch").length > 1 compose = "--compose" + subject = "--subject \"#{type} #{name} against #{parent}\"" else compose = "" + subject = "" end # Now send the mail. - sh "git send-email #{compose} --no-signed-off-by-cc --suppress-from --to puppet-dev@googlegroups.com 00*.patch" + sh "git send-email #{compose} #{subject} --no-signed-off-by-cc --suppress-from --to puppet-dev@googlegroups.com 00*.patch" # Finally, clean up the patches sh "rm 00*.patch" diff --git a/test/ral/type/exec.rb b/test/ral/type/exec.rb index 5b26a98a2..dd42ae61f 100755 --- a/test/ral/type/exec.rb +++ b/test/ral/type/exec.rb @@ -34,10 +34,8 @@ class TestExec < Test::Unit::TestCase assert_nothing_raised { command = Puppet::Type.type(:exec).new( - :command => "echo", - - :path => "/usr/bin:/bin:/usr/sbin:/sbin" + :path => "/usr/bin:/bin:/usr/sbin:/sbin" ) } assert_nothing_raised { @@ -48,10 +46,8 @@ class TestExec < Test::Unit::TestCase assert_nothing_raised { command = Puppet::Type.type(:exec).new( - :command => "/bin/echo", - - :path => "/usr/bin:/bin:/usr/sbin:/sbin" + :path => "/usr/bin:/bin:/usr/sbin:/sbin" ) } end @@ -60,30 +56,24 @@ class TestExec < Test::Unit::TestCase assert_nothing_raised { command = Puppet::Type.type(:exec).new( - :command => "mkdir /this/directory/does/not/exist", - :path => "/usr/bin:/bin:/usr/sbin:/sbin", - + :path => "/usr/bin:/bin:/usr/sbin:/sbin", :returns => 1 ) } assert_nothing_raised { command = Puppet::Type.type(:exec).new( - :command => "touch /etc", - :path => "/usr/bin:/bin:/usr/sbin:/sbin", - + :path => "/usr/bin:/bin:/usr/sbin:/sbin", :returns => 1 ) } assert_nothing_raised { command = Puppet::Type.type(:exec).new( - :command => "thiscommanddoesnotexist", - :path => "/usr/bin:/bin:/usr/sbin:/sbin", - + :path => "/usr/bin:/bin:/usr/sbin:/sbin", :returns => 127 ) } @@ -98,11 +88,9 @@ class TestExec < Test::Unit::TestCase assert_nothing_raised { command = Puppet::Type.type(:exec).new( - :command => "pwd", - :cwd => dir, - :path => "/usr/bin:/bin:/usr/sbin:/sbin", - + :cwd => dir, + :path => "/usr/bin:/bin:/usr/sbin:/sbin", :returns => 0 ) } @@ -117,11 +105,9 @@ class TestExec < Test::Unit::TestCase @@tmpfiles.push tmpfile trans = nil - file = Puppet::Type.type(:file).new( - - :path => tmpfile, - - :content => "yay" + file = Puppet::Type.type(:file).new( + :path => tmpfile, + :content => "yay" ) # Get the file in sync assert_apply(file) @@ -129,13 +115,10 @@ class TestExec < Test::Unit::TestCase # Now make an exec maker = tempfile assert_nothing_raised { - cmd = Puppet::Type.type(:exec).new( - - :command => "touch #{maker}", - :path => "/usr/bin:/bin:/usr/sbin:/sbin", - :subscribe => file, - + :command => "touch #{maker}", + :path => "/usr/bin:/bin:/usr/sbin:/sbin", + :subscribe => file, :refreshonly => true ) } @@ -143,7 +126,7 @@ class TestExec < Test::Unit::TestCase assert(cmd, "did not make exec") assert_nothing_raised do - assert(! cmd.check, "Check passed when refreshonly is set") + assert(! cmd.check_all_attributes, "Check passed when refreshonly is set") end assert_events([], file, cmd) @@ -158,25 +141,22 @@ class TestExec < Test::Unit::TestCase def test_refreshonly cmd = true assert_nothing_raised { - cmd = Puppet::Type.type(:exec).new( - - :command => "pwd", - :path => "/usr/bin:/bin:/usr/sbin:/sbin", - + :command => "pwd", + :path => "/usr/bin:/bin:/usr/sbin:/sbin", :refreshonly => true ) } # Checks should always fail when refreshonly is enabled - assert(!cmd.check, "Check passed with refreshonly true") + assert(!cmd.check_all_attributes, "Check passed with refreshonly true") # Now make sure it passes if we pass in "true" - assert(cmd.check(true), "Check failed with refreshonly true while refreshing") + assert(cmd.check_all_attributes(true), "Check failed with refreshonly true while refreshing") # Now set it to false cmd[:refreshonly] = false - assert(cmd.check, "Check failed with refreshonly false") + assert(cmd.check_all_attributes, "Check failed with refreshonly false") end def test_creates @@ -186,10 +166,8 @@ class TestExec < Test::Unit::TestCase assert_nothing_raised { exec = Puppet::Type.type(:exec).new( - :command => "touch #{file}", - :path => "/usr/bin:/bin:/usr/sbin:/sbin", - + :path => "/usr/bin:/bin:/usr/sbin:/sbin", :creates => file ) } @@ -206,21 +184,15 @@ class TestExec < Test::Unit::TestCase sh = %x{which sh} File.open(exe, "w") { |f| f.puts "#!#{sh}\necho yup" } - - file = Puppet::Type.type(:file).new( - - :path => oexe, - :source => exe, - - :mode => 0755 + file = Puppet::Type.type(:file).new( + :path => oexe, + :source => exe, + :mode => 0755 ) - - exec = Puppet::Type.type(:exec).new( - - :command => oexe, - - :require => Puppet::Resource.new(:file, oexe) + exec = Puppet::Type.type(:exec).new( + :command => oexe, + :require => Puppet::Resource.new(:file, oexe) ) comp = mk_catalog("Testing", file, exec) @@ -236,47 +208,36 @@ class TestExec < Test::Unit::TestCase File.open(exe, "w") { |f| f.puts "#!#{sh}\necho yup" } - file = Puppet::Type.type(:file).new( - - :path => oexe, - :source => exe, - - :mode => 755 + file = Puppet::Type.type(:file).new( + :path => oexe, + :source => exe, + :mode => 755 ) basedir = File.dirname(oexe) - baseobj = Puppet::Type.type(:file).new( - - :path => basedir, - :source => exe, - - :mode => 755 + baseobj = Puppet::Type.type(:file).new( + :path => basedir, + :source => exe, + :mode => 755 ) - ofile = Puppet::Type.type(:file).new( - - :path => exe, - - :mode => 755 + ofile = Puppet::Type.type(:file).new( + :path => exe, + :mode => 755 ) - exec = Puppet::Type.type(:exec).new( - - :command => oexe, - :path => ENV["PATH"], - - :cwd => basedir + exec = Puppet::Type.type(:exec).new( + :command => oexe, + :path => ENV["PATH"], + :cwd => basedir ) - - cat = Puppet::Type.type(:exec).new( - - :command => "cat #{exe} #{oexe}", - - :path => ENV["PATH"] + cat = Puppet::Type.type(:exec).new( + :command => "cat #{exe} #{oexe}", + :path => ENV["PATH"] ) catalog = mk_catalog(file, baseobj, ofile, exec, cat) @@ -311,11 +272,9 @@ class TestExec < Test::Unit::TestCase assert_nothing_raised { exec = Puppet::Type.type(:exec).new( - :command => "touch #{bfile}", - :onlyif => "test -f #{afile}", - - :path => ENV['PATH'] + :onlyif => "test -f #{afile}", + :path => ENV['PATH'] ) } @@ -333,12 +292,9 @@ class TestExec < Test::Unit::TestCase exec = nil assert_nothing_raised { - exec = Puppet::Type.type(:exec).new( - :command => "touch #{bfile}", :unless => "test -f #{afile}", - :path => ENV['PATH'] ) } @@ -417,13 +373,10 @@ class TestExec < Test::Unit::TestCase def test_logoutput exec = nil assert_nothing_raised { - exec = Puppet::Type.type(:exec).new( - - :title => "logoutputesting", - :path => "/usr/bin:/bin", - :command => "echo logoutput is false", - + :title => "logoutputesting", + :path => "/usr/bin:/bin", + :command => "echo logoutput is false", :logoutput => false ) } @@ -453,24 +406,19 @@ class TestExec < Test::Unit::TestCase assert_nothing_raised { exec = Puppet::Type.type(:exec).new( - - :title => "mkdir", - :path => "/usr/bin:/bin", + :title => "mkdir", + :path => "/usr/bin:/bin", :creates => basedir, - :command => "mkdir #{basedir}; touch #{path}" - ) } assert_nothing_raised { file = Puppet::Type.type(:file).new( - - :path => basedir, + :path => basedir, :recurse => true, - :mode => "755", - + :mode => "755", :require => Puppet::Resource.new("exec", "mkdir") ) } @@ -507,12 +455,9 @@ class TestExec < Test::Unit::TestCase file = tempfile assert_nothing_raised { - exec1 = Puppet::Type.type(:exec).new( - - :title => "one", - :path => ENV["PATH"], - + :title => "one", + :path => ENV["PATH"], :command => "mkdir #{dir}" ) } @@ -520,12 +465,10 @@ class TestExec < Test::Unit::TestCase assert_nothing_raised("Could not create exec w/out existing cwd") { exec2 = Puppet::Type.type(:exec).new( - - :title => "two", - :path => ENV["PATH"], + :title => "two", + :path => ENV["PATH"], :command => "touch #{file}", - - :cwd => dir + :cwd => dir ) } @@ -555,11 +498,8 @@ class TestExec < Test::Unit::TestCase test = "test -f #{file}" assert_nothing_raised { - exec = Puppet::Type.type(:exec).new( - - :path => ENV["PATH"], - + :path => ENV["PATH"], :command => "touch #{file}" ) } @@ -569,7 +509,7 @@ class TestExec < Test::Unit::TestCase } assert_nothing_raised { - assert(exec.check, "Check did not pass") + assert(exec.check_all_attributes, "Check did not pass") } assert_nothing_raised { @@ -582,13 +522,13 @@ class TestExec < Test::Unit::TestCase } assert_nothing_raised { - assert(exec.check, "Check did not pass") + assert(exec.check_all_attributes, "Check did not pass") } assert_apply(exec) assert_nothing_raised { - assert(! exec.check, "Check passed") + assert(! exec.check_all_attributes, "Check passed") } end @@ -597,33 +537,29 @@ class TestExec < Test::Unit::TestCase return if Facter.value(:operatingsystem) == "Solaris" exec = Puppet::Type.type(:exec).new( - :command => "echo true", - :path => ENV["PATH"], - - :onlyif => "/bin/nosuchthingexists" - ) + :path => ENV["PATH"], + :onlyif => "/bin/nosuchthingexists" + ) assert_raise(ArgumentError, "Missing command did not raise error") { - exec.run("/bin/nosuchthingexists") + exec.provider.run("/bin/nosuchthingexists") } end def test_envparam exec = Puppet::Type.newexec( - :command => "echo $envtest", - :path => ENV["PATH"], - - :env => "envtest=yayness" + :path => ENV["PATH"], + :env => "envtest=yayness" ) assert(exec, "Could not make exec") output = status = nil assert_nothing_raised { - output, status = exec.run("echo $envtest") + output, status = exec.provider.run("echo $envtest") } assert_equal("yayness\n", output) @@ -636,7 +572,7 @@ and stuff" output = status = nil assert_nothing_raised { - output, status = exec.run('echo "$envtest"') + output, status = exec.provider.run('echo "$envtest"') } assert_equal("a list of things\nand stuff\n", output) @@ -647,7 +583,7 @@ and stuff" output = status = nil assert_nothing_raised { - output, status = exec.run('echo "$funtest" "$yaytest"') + output, status = exec.provider.run('echo "$funtest" "$yaytest"') } assert_equal("A B\n", output) end @@ -655,10 +591,8 @@ and stuff" def test_environmentparam exec = Puppet::Type.newexec( - - :command => "echo $environmenttest", - :path => ENV["PATH"], - + :command => "echo $environmenttest", + :path => ENV["PATH"], :environment => "environmenttest=yayness" ) @@ -666,7 +600,7 @@ and stuff" output = status = nil assert_nothing_raised { - output, status = exec.run("echo $environmenttest") + output, status = exec.provider.run("echo $environmenttest") } assert_equal("yayness\n", output) @@ -679,7 +613,7 @@ and stuff" output = status = nil assert_nothing_raised { - output, status = exec.run('echo "$environmenttest"') + output, status = exec.provider.run('echo "$environmenttest"') } assert_equal("a list of things\nand stuff\n", output) @@ -690,36 +624,20 @@ and stuff" output = status = nil assert_nothing_raised { - output, status = exec.run('echo "$funtest" "$yaytest"') + output, status = exec.provider.run('echo "$funtest" "$yaytest"') } assert_equal("A B\n", output) end - def test_timeout - exec = Puppet::Type.type(:exec).new(:command => "sleep 1", :path => ENV["PATH"], :timeout => "0.2") - time = Time.now - - assert_raise(Timeout::Error) { - exec.run("sleep 1") - } - Puppet.info "#{Time.now.to_f - time.to_f} seconds, vs a timeout of #{exec[:timeout]}" - - - assert_apply(exec) - end - # Testing #470 def test_run_as_created_user exec = nil if Process.uid == 0 user = "nosuchuser" assert_nothing_raised("Could not create exec with non-existent user") do - exec = Puppet::Type.type(:exec).new( - :command => "/bin/echo yay", - - :user => user + :user => user ) end end @@ -729,10 +647,8 @@ and stuff" assert_nothing_raised("Could not create exec with non-existent user") do exec = Puppet::Type.type(:exec).new( - :command => "/bin/echo yay", - - :group => group + :group => group ) end end @@ -759,12 +675,10 @@ and stuff" file = tempfile maker = tempfile - exec = Puppet::Type.type(:exec).new( - - :title => "maker", - :command => "touch #{maker}", - - :path => ENV["PATH"] + exec = Puppet::Type.type(:exec).new( + :title => "maker", + :command => "touch #{maker}", + :path => ENV["PATH"] ) # Make sure it runs normally @@ -801,12 +715,10 @@ and stuff" refresher = tempfile maker = tempfile - exec = Puppet::Type.type(:exec).new( - - :title => "maker", - :command => "touch #{maker}", - - :path => ENV["PATH"] + exec = Puppet::Type.type(:exec).new( + :title => "maker", + :command => "touch #{maker}", + :path => ENV["PATH"] ) # Call refresh normally @@ -844,4 +756,3 @@ and stuff" end end end - |