Skip to content
Browse files

Merge branch '2.6.next' into 2.6.x

* 2.6.next: (23 commits)
  (#5908) Add support for new update-rc.d disable API
  (#6862) Add a default subject for the mail_patches rake task
  Fixed #6256 - Creation of rrd directory.
  (#5477) Allow watch_file to watch non-existent files, especially site.pp
  (#5221) Add test for fix to fileset with trailing separator
  (#5221) Fix fileset path absoluteness checking with trailing slash
  (#4769) Fix negative timeout support for newer rubies
  Fixed #6562 - Minor kick documentation fix
  (#6658) Propagate ENC connection errors to the agent
  (#4884) Remove typo from spec test
  (#4884) Modify tests to pass on non-OS X systems
  (#4884) Revise new exec tests, add a few more
  (4576) - if ENC declares invalid class, it is logged at warning.
  (#4884) Add an shell provider for execs
  (#4884) Fix Test::Unit exec tests
  (#4884) Break the exec type out to have a posix provider
  (#4884) Add consistent path validation and behavior
  (#4884) Add expand_path to requiring the spec_helper
  (#4884) Autorequire shared behaviors and method to silence warnings
  (#4884) Fix whitespace
  ...
  • Loading branch information...
2 parents 76b3ee6 + f5170fc commit 9d17809e5b240aa80d2c30f4d9625d4812802b8f @MaxMartin MaxMartin committed Mar 30, 2011
Showing with 1,874 additions and 528 deletions.
  1. +1 −1 lib/puppet/application/master.rb
  2. +1 −0 lib/puppet/defaults.rb
  3. +1 −0 lib/puppet/file_serving/fileset.rb
  4. +1 −2 lib/puppet/indirector/exec.rb
  5. +2 −0 lib/puppet/parameter.rb
  6. +42 −0 lib/puppet/parameter/path.rb
  7. +1 −1 lib/puppet/parser/compiler.rb
  8. +3 −2 lib/puppet/parser/lexer.rb
  9. +0 −1 lib/puppet/parser/parser_support.rb
  10. +112 −0 lib/puppet/provider/exec/posix.rb
  11. +17 −0 lib/puppet/provider/exec/shell.rb
  12. +6 −2 lib/puppet/provider/service/debian.rb
  13. +0 −1 lib/puppet/resource/type_collection.rb
  14. +1 −1 lib/puppet/simple_graph.rb
  15. +30 −167 lib/puppet/type/exec.rb
  16. +0 −1 lib/puppet/util/command_line/puppetrun
  17. +1 −5 lib/puppet/util/loadedfile.rb
  18. +9 −15 spec/integration/transaction_spec.rb
  19. +9 −0 spec/lib/puppet_spec/verbose.rb
  20. +185 −0 spec/shared_behaviours/path_parameters.rb
  21. +6 −0 spec/spec_helper.rb
  22. +2 −2 spec/unit/application/master_spec.rb
  23. +8 −0 spec/unit/file_serving/fileset_spec.rb
  24. +2 −3 spec/unit/indirector/exec_spec.rb
  25. +24 −0 spec/unit/parameter/path_spec.rb
  26. +1 −2 spec/unit/parser/compiler_spec.rb
  27. +12 −0 spec/unit/parser/lexer_spec.rb
  28. +120 −0 spec/unit/provider/exec/posix_spec.rb
  29. +50 −0 spec/unit/provider/exec/shell_spec.rb
  30. +14 −2 spec/unit/provider/service/debian_spec.rb
  31. +14 −17 spec/unit/resource/type_collection_spec.rb
  32. +466 −18 spec/unit/type/cron_spec.rb
  33. +633 −106 spec/unit/type/exec_spec.rb
  34. +1 −1 spec/unit/type_spec.rb
  35. +7 −0 spec/unit/util/loadedfile_spec.rb
  36. +2 −1 spec/unit/util/rdoc/parser_spec.rb
  37. +3 −1 tasks/rake/git_workflow.rake
  38. +87 −176 test/ral/type/exec.rb
View
2 lib/puppet/application/master.rb
@@ -136,7 +136,7 @@ def setup
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
View
1 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.
View
1 lib/puppet/file_serving/fileset.rb
@@ -59,6 +59,7 @@ def ignore=(values)
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
View
3 lib/puppet/indirector/exec.rb
@@ -35,8 +35,7 @@ def query(name)
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
View
2 lib/puppet/parameter.rb
@@ -300,3 +300,5 @@ def to_s
name.to_s
end
end
+
+require 'puppet/parameter/path'
View
42 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
View
2 lib/puppet/parser/compiler.rb
@@ -162,7 +162,7 @@ def evaluate_classes(classes, scope, lazy_evaluate = true)
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
View
5 lib/puppet/parser/lexer.rb
@@ -312,7 +312,8 @@ def fullscan
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 @@ def tokenize_interpolated_string(token_type,preamble='')
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)
View
1 lib/puppet/parser/parser_support.rb
@@ -88,7 +88,6 @@ def file=(file)
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)
View
112 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
View
17 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
View
8 lib/puppet/provider/service/debian.rb
@@ -22,8 +22,12 @@ def self.defpath
# 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?
View
1 lib/puppet/resource/type_collection.rb
@@ -162,7 +162,6 @@ def perform_initial_import
parser.string = code
else
file = Puppet.settings.value(:manifest, environment.to_s)
- return unless File.exist?(file)
parser.file = file
end
parser.parse
View
2 lib/puppet/simple_graph.rb
@@ -329,7 +329,7 @@ def splice!(other, type)
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
View
197 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 @@ def change_to_s(currentvalue, newvalue)
# 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 @@ def sync
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 @@ def sync
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 @@ def value=(*values)
# 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 @@ def value=(*values)
for refreshing."
validate do |command|
- @resource.validatecmd(command)
+ provider.validatecmd(command)
end
end
@@ -241,19 +229,17 @@ def value=(*values)
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 @@ def check(value)
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 @@ def check(value)
"
- # 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 @@ def check(value)
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 @@ def check(value)
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 @@ def check(value)
@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 @@ def self.instances
# 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 @@ def check(refreshing = false)
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 @@ def output
# 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
View
1 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
View
6 lib/puppet/util/loadedfile.rb
@@ -34,10 +34,6 @@ def changed?
# 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 @@ def stamp
@statted = Time.now.to_i
begin
@stamp = File.stat(@file).ctime
- rescue Errno::ENOENT
+ rescue Errno::ENOENT, Errno::ENOTDIR
@stamp = Time.now
end
end
View
24 spec/integration/transaction_spec.rb
@@ -107,29 +107,23 @@ def mk_catalog(*resources)
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)
View
9 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
View
185 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
View
6 spec/spec_helper.rb
@@ -23,10 +23,16 @@ module PuppetSpec
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
View
4 spec/unit/application/master_spec.rb
@@ -176,8 +176,8 @@
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
View
8 spec/unit/file_serving/fileset_spec.rb
@@ -13,6 +13,14 @@
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)
View
5 spec/unit/indirector/exec_spec.rb
@@ -47,10 +47,9 @@ def self.to_s
@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
View
24 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
View
3 spec/unit/parser/compiler_spec.rb
@@ -581,12 +581,11 @@ def add_resource(name, parent = nil)
@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
View
12 spec/unit/parser/lexer_spec.rb
@@ -679,3 +679,15 @@ def tokens_scanned_from(s)
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
View
120 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
View
50 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
View
16 spec/unit/provider/service/debian_spec.rb
@@ -52,8 +52,20 @@
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
View
31 spec/unit/resource/type_collection_spec.rb
@@ -6,6 +6,7 @@
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 @@
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 "when performing initial import" do
before do
- @parser = stub 'parser', :file= => nil, :string => nil, :parse => nil
+ @parser = stub 'parser'
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,26 +400,27 @@
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
end
View
484 spec/unit/type/cron_spec.rb
@@ -4,30 +4,478 @@
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
View
739 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