diff --git a/lib/puppet/feature/ansicolor.rb b/lib/puppet/feature/ansicolor.rb
deleted file mode 100644
index 74ec1a49c..000000000
--- a/lib/puppet/feature/ansicolor.rb
+++ /dev/null
@@ -1,5 +0,0 @@
-require 'puppet/util/feature'
-
-Puppet.features.rubygems?
-Puppet.features.add(:ansicolor,
- Puppet.features.microsoft_windows? ? { :libs => 'win32console' } : {} )
diff --git a/lib/puppet/provider/package/gem.rb b/lib/puppet/provider/package/gem.rb
index 9e483530a..3ede09f8e 100755
--- a/lib/puppet/provider/package/gem.rb
+++ b/lib/puppet/provider/package/gem.rb
@@ -1,123 +1,123 @@
require 'puppet/provider/package'
require 'uri'
# Ruby gems support.
Puppet::Type.type(:package).provide :gem, :parent => Puppet::Provider::Package do
desc "Ruby Gem support. If a URL is passed via `source`, then that URL is used as the
remote gem repository; if a source is present but is not a valid URL, it will be
interpreted as the path to a local gem file. If source is not present at all,
the gem will be installed from the default gem repositories."
has_feature :versionable
commands :gemcmd => "gem"
def self.gemlist(options)
gem_list_command = [command(:gemcmd), "list"]
if options[:local]
gem_list_command << "--local"
else
gem_list_command << "--remote"
end
if name = options[:justme]
gem_list_command << name + "$"
end
begin
list = execute(gem_list_command).lines.
- map {|set| gemsplit(set) }.
- reject {|item| item.nil? }
+ map {|set| gemsplit(set) }.
+ reject {|x| x.nil? }
rescue Puppet::ExecutionFailure => detail
raise Puppet::Error, "Could not list gems: #{detail}"
end
if options[:justme]
return list.shift
else
return list
end
end
def self.gemsplit(desc)
# `gem list` when output console has a line like:
# *** LOCAL GEMS ***
# but when it's not to the console that line
# and all blank lines are stripped
# so we don't need to check for them
if desc =~ /^(\S+)\s+\((.+)\)/
name = $1
versions = $2.split(/,\s*/)
{
:name => name,
:ensure => versions,
:provider => :gem
}
else
Puppet.warning "Could not match #{desc}" unless desc.chomp.empty?
nil
end
end
def self.instances(justme = false)
gemlist(:local => true).collect do |hash|
new(hash)
end
end
def install(useversion = true)
command = [command(:gemcmd), "install"]
command << "-v" << resource[:ensure] if (! resource[:ensure].is_a? Symbol) and useversion
# Always include dependencies
command << "--include-dependencies"
if source = resource[:source]
begin
uri = URI.parse(source)
rescue => detail
fail "Invalid source '#{uri}': #{detail}"
end
case uri.scheme
when nil
# no URI scheme => interpret the source as a local file
command << source
when /file/i
command << uri.path
when 'puppet'
# we don't support puppet:// URLs (yet)
raise Puppet::Error.new("puppet:// URLs are not supported as gem sources")
else
# interpret it as a gem repository
command << "--source" << "#{source}" << resource[:name]
end
else
command << "--no-rdoc" << "--no-ri" << resource[:name]
end
output = execute(command)
# Apparently some stupid gem versions don't exit non-0 on failure
self.fail "Could not install: #{output.chomp}" if output.include?("ERROR")
end
def latest
# This always gets the latest version available.
hash = self.class.gemlist(:justme => resource[:name])
hash[:ensure][0]
end
def query
self.class.gemlist(:justme => resource[:name], :local => true)
end
def uninstall
gemcmd "uninstall", "-x", "-a", resource[:name]
end
def update
self.install(false)
end
end
diff --git a/lib/puppet/util.rb b/lib/puppet/util.rb
index 4bcf89061..ff6b7f2d2 100644
--- a/lib/puppet/util.rb
+++ b/lib/puppet/util.rb
@@ -1,583 +1,584 @@
# A module to collect utility functions.
require 'English'
require 'puppet/external/lock'
require 'puppet/error'
require 'puppet/util/execution_stub'
require 'uri'
require 'sync'
require 'monitor'
require 'tempfile'
require 'pathname'
require 'ostruct'
+require 'puppet/util/platform'
module Puppet
module Util
require 'puppet/util/monkey_patches'
require 'benchmark'
# These are all for backward compatibility -- these are methods that used
# to be in Puppet::Util but have been moved into external modules.
require 'puppet/util/posix'
extend Puppet::Util::POSIX
@@sync_objects = {}.extend MonitorMixin
def self.activerecord_version
if (defined?(::ActiveRecord) and defined?(::ActiveRecord::VERSION) and defined?(::ActiveRecord::VERSION::MAJOR) and defined?(::ActiveRecord::VERSION::MINOR))
([::ActiveRecord::VERSION::MAJOR, ::ActiveRecord::VERSION::MINOR].join('.').to_f)
else
0
end
end
# Run some code with a specific environment. Resets the environment back to
# what it was at the end of the code.
def self.withenv(hash)
saved = ENV.to_hash
hash.each do |name, val|
ENV[name.to_s] = val
end
yield
ensure
ENV.clear
saved.each do |name, val|
ENV[name] = val
end
end
# Execute a given chunk of code with a new umask.
def self.withumask(mask)
cur = File.umask(mask)
begin
yield
ensure
File.umask(cur)
end
end
def self.synchronize_on(x,type)
sync_object,users = 0,1
begin
@@sync_objects.synchronize {
(@@sync_objects[x] ||= [Sync.new,0])[users] += 1
}
@@sync_objects[x][sync_object].synchronize(type) { yield }
ensure
@@sync_objects.synchronize {
@@sync_objects.delete(x) unless (@@sync_objects[x][users] -= 1) > 0
}
end
end
# Change the process to a different user
def self.chuser
if group = Puppet[:group]
begin
Puppet::Util::SUIDManager.change_group(group, true)
rescue => detail
Puppet.warning "could not change to group #{group.inspect}: #{detail}"
$stderr.puts "could not change to group #{group.inspect}"
# Don't exit on failed group changes, since it's
# not fatal
#exit(74)
end
end
if user = Puppet[:user]
begin
Puppet::Util::SUIDManager.change_user(user, true)
rescue => detail
$stderr.puts "Could not change to user #{user}: #{detail}"
exit(74)
end
end
end
# Create instance methods for each of the log levels. This allows
# the messages to be a little richer. Most classes will be calling this
# method.
def self.logmethods(klass, useself = true)
Puppet::Util::Log.eachlevel { |level|
klass.send(:define_method, level, proc { |args|
args = args.join(" ") if args.is_a?(Array)
if useself
Puppet::Util::Log.create(
:level => level,
:source => self,
:message => args
)
else
Puppet::Util::Log.create(
:level => level,
:message => args
)
end
})
}
end
# Proxy a bunch of methods to another object.
def self.classproxy(klass, objmethod, *methods)
classobj = class << klass; self; end
methods.each do |method|
classobj.send(:define_method, method) do |*args|
obj = self.send(objmethod)
obj.send(method, *args)
end
end
end
# Proxy a bunch of methods to another object.
def self.proxy(klass, objmethod, *methods)
methods.each do |method|
klass.send(:define_method, method) do |*args|
obj = self.send(objmethod)
obj.send(method, *args)
end
end
end
def benchmark(*args)
msg = args.pop
level = args.pop
object = nil
if args.empty?
if respond_to?(level)
object = self
else
object = Puppet
end
else
object = args.pop
end
raise Puppet::DevError, "Failed to provide level to :benchmark" unless level
unless level == :none or object.respond_to? level
raise Puppet::DevError, "Benchmarked object does not respond to #{level}"
end
# Only benchmark if our log level is high enough
if level != :none and Puppet::Util::Log.sendlevel?(level)
result = nil
seconds = Benchmark.realtime {
yield
}
object.send(level, msg + (" in %0.2f seconds" % seconds))
return seconds
else
yield
end
end
def which(bin)
if absolute_path?(bin)
return bin if FileTest.file? bin and FileTest.executable? bin
else
ENV['PATH'].split(File::PATH_SEPARATOR).each do |dir|
begin
dest = File.expand_path(File.join(dir, bin))
rescue ArgumentError => e
# if the user's PATH contains a literal tilde (~) character and HOME is not set, we may get
# an ArgumentError here. Let's check to see if that is the case; if not, re-raise whatever error
# was thrown.
if e.to_s =~ /HOME/ and (ENV['HOME'].nil? || ENV['HOME'] == "")
# if we get here they have a tilde in their PATH. We'll issue a single warning about this and then
# ignore this path element and carry on with our lives.
Puppet::Util::Warnings.warnonce("PATH contains a ~ character, and HOME is not set; ignoring PATH element '#{dir}'.")
elsif e.to_s =~ /doesn't exist|can't find user/
# ...otherwise, we just skip the non-existent entry, and do nothing.
Puppet::Util::Warnings.warnonce("Couldn't expand PATH containing a ~ character; ignoring PATH element '#{dir}'.")
else
raise
end
else
if Puppet.features.microsoft_windows? && File.extname(dest).empty?
exts = ENV['PATHEXT']
exts = exts ? exts.split(File::PATH_SEPARATOR) : %w[.COM .EXE .BAT .CMD]
exts.each do |ext|
destext = File.expand_path(dest + ext)
return destext if FileTest.file? destext and FileTest.executable? destext
end
end
return dest if FileTest.file? dest and FileTest.executable? dest
end
end
end
nil
end
module_function :which
# Determine in a platform-specific way whether a path is absolute. This
# defaults to the local platform if none is specified.
def absolute_path?(path, platform=nil)
# Escape once for the string literal, and once for the regex.
slash = '[\\\\/]'
name = '[^\\\\/]+'
regexes = {
:windows => %r!^(([A-Z]:#{slash})|(#{slash}#{slash}#{name}#{slash}#{name})|(#{slash}#{slash}\?#{slash}#{name}))!i,
:posix => %r!^/!,
}
# Due to weird load order issues, I was unable to remove this require.
# This is fixed in Telly so it can be removed there.
require 'puppet'
# Ruby only sets File::ALT_SEPARATOR on Windows and the Ruby standard
# library uses that to test what platform it's on. Normally in Puppet we
# would use Puppet.features.microsoft_windows?, but this method needs to
# be called during the initialization of features so it can't depend on
# that.
- platform ||= File::ALT_SEPARATOR ? :windows : :posix
+ platform ||= Puppet::Util::Platform.windows? ? :windows : :posix
!! (path =~ regexes[platform])
end
module_function :absolute_path?
# Convert a path to a file URI
def path_to_uri(path)
return unless path
params = { :scheme => 'file' }
if Puppet.features.microsoft_windows?
path = path.gsub(/\\/, '/')
if unc = /^\/\/([^\/]+)(\/[^\/]+)/.match(path)
params[:host] = unc[1]
path = unc[2]
elsif path =~ /^[a-z]:\//i
path = '/' + path
end
end
params[:path] = URI.escape(path)
begin
URI::Generic.build(params)
rescue => detail
raise Puppet::Error, "Failed to convert '#{path}' to URI: #{detail}"
end
end
module_function :path_to_uri
# Get the path component of a URI
def uri_to_path(uri)
return unless uri.is_a?(URI)
path = URI.unescape(uri.path)
if Puppet.features.microsoft_windows? and uri.scheme == 'file'
if uri.host
path = "//#{uri.host}" + path # UNC
else
path.sub!(/^\//, '')
end
end
path
end
module_function :uri_to_path
def safe_posix_fork(stdin=$stdin, stdout=$stdout, stderr=$stderr, &block)
child_pid = Kernel.fork do
$stdin.reopen(stdin)
$stdout.reopen(stdout)
$stderr.reopen(stderr)
3.upto(256){|fd| IO::new(fd).close rescue nil}
block.call if block
end
child_pid
end
module_function :safe_posix_fork
# Create an exclusive lock.
def threadlock(resource, type = Sync::EX)
Puppet::Util.synchronize_on(resource,type) { yield }
end
module_function :benchmark
def memory
unless defined?(@pmap)
@pmap = which('pmap')
end
if @pmap
%x{#{@pmap} #{Process.pid}| grep total}.chomp.sub(/^\s*total\s+/, '').sub(/K$/, '').to_i
else
0
end
end
def symbolize(value)
if value.respond_to? :intern
value.intern
else
value
end
end
def symbolizehash(hash)
newhash = {}
hash.each do |name, val|
if name.is_a? String
newhash[name.intern] = val
else
newhash[name] = val
end
end
newhash
end
def symbolizehash!(hash)
# this is not the most memory-friendly way to accomplish this, but the
# code re-use and clarity seems worthwhile.
newhash = symbolizehash(hash)
hash.clear
hash.merge!(newhash)
hash
end
module_function :symbolize, :symbolizehash, :symbolizehash!
# Just benchmark, with no logging.
def thinmark
seconds = Benchmark.realtime {
yield
}
seconds
end
module_function :memory, :thinmark
# Because IO#binread is only available in 1.9
def binread(file)
File.open(file, 'rb') { |f| f.read }
end
module_function :binread
# utility method to get the current call stack and format it to a human-readable string (which some IDEs/editors
# will recognize as links to the line numbers in the trace)
def self.pretty_backtrace(backtrace = caller(1))
backtrace.collect do |line|
file_path, line_num = line.split(":")
file_path = expand_symlinks(File.expand_path(file_path))
file_path + ":" + line_num
end .join("\n")
end
# utility method that takes a path as input, checks each component of the path to see if it is a symlink, and expands
# it if it is. returns the expanded path.
def self.expand_symlinks(file_path)
file_path.split("/").inject do |full_path, next_dir|
next_path = full_path + "/" + next_dir
if File.symlink?(next_path) then
link = File.readlink(next_path)
next_path =
case link
when /^\// then link
else
File.expand_path(full_path + "/" + link)
end
end
next_path
end
end
# Replace a file, securely. This takes a block, and passes it the file
# handle of a file open for writing. Write the replacement content inside
# the block and it will safely replace the target file.
#
# This method will make no changes to the target file until the content is
# successfully written and the block returns without raising an error.
#
# As far as possible the state of the existing file, such as mode, is
# preserved. This works hard to avoid loss of any metadata, but will result
# in an inode change for the file.
#
# Arguments: `filename`, `default_mode`
#
# The filename is the file we are going to replace.
#
# The default_mode is the mode to use when the target file doesn't already
# exist; if the file is present we copy the existing mode/owner/group values
# across.
def replace_file(file, default_mode, &block)
raise Puppet::DevError, "replace_file requires a block" unless block_given?
file = Pathname(file)
tempfile = Tempfile.new(file.basename.to_s, file.dirname.to_s)
file_exists = file.exist?
# Set properties of the temporary file before we write the content, because
# Tempfile doesn't promise to be safe from reading by other people, just
# that it avoids races around creating the file.
#
# Our Windows emulation is pretty limited, and so we have to carefully
# and specifically handle the platform, which has all sorts of magic.
# So, unlike Unix, we don't pre-prep security; we use the default "quite
# secure" tempfile permissions instead. Magic happens later.
unless Puppet.features.microsoft_windows?
# Grab the current file mode, and fall back to the defaults.
stat = file.lstat rescue OpenStruct.new(:mode => default_mode,
:uid => Process.euid,
:gid => Process.egid)
# We only care about the bottom four slots, which make the real mode,
# and not the rest of the platform stat call fluff and stuff.
tempfile.chmod(stat.mode & 07777)
tempfile.chown(stat.uid, stat.gid)
end
# OK, now allow the caller to write the content of the file.
yield tempfile
# Now, make sure the data (which includes the mode) is safe on disk.
tempfile.flush
begin
tempfile.fsync
rescue NotImplementedError
# fsync may not be implemented by Ruby on all platforms, but
# there is absolutely no recovery path if we detect that. So, we just
# ignore the return code.
#
# However, don't be fooled: that is accepting that we are running in
# an unsafe fashion. If you are porting to a new platform don't stub
# that out.
end
tempfile.close
if Puppet.features.microsoft_windows?
# This will appropriately clone the file, but only if the file we are
# replacing exists. Which is kind of annoying; thanks Microsoft.
#
# So, to avoid getting into an infinite loop we will retry once if the
# file doesn't exist, but only the once...
have_retried = false
begin
# Yes, the arguments are reversed compared to the rename in the rest
# of the world.
Puppet::Util::Windows::File.replace_file(file, tempfile.path)
rescue Puppet::Util::Windows::Error => e
# This might race, but there are enough possible cases that there
# isn't a good, solid "better" way to do this, and the next call
# should fail in the same way anyhow.
raise if have_retried or File.exist?(file)
have_retried = true
# OK, so, we can't replace a file that doesn't exist, so let us put
# one in place and set the permissions. Then we can retry and the
# magic makes this all work.
#
# This is the least-worst option for handling Windows, as far as we
# can determine.
File.open(file, 'a') do |fh|
# this space deliberately left empty for auto-close behaviour,
# append mode, and not actually changing any of the content.
end
# Set the permissions to what we want.
Puppet::Util::Windows::Security.set_mode(default_mode, file.to_s)
# ...and finally retry the operation.
retry
end
else
File.rename(tempfile.path, file)
end
# Ideally, we would now fsync the directory as well, but Ruby doesn't
# have support for that, and it doesn't matter /that/ much...
# Return something true, and possibly useful.
file
end
module_function :replace_file
# Executes a block of code, wrapped with some special exception handling. Causes the ruby interpreter to
# exit if the block throws an exception.
#
# @param [String] message a message to log if the block fails
# @param [Integer] code the exit code that the ruby interpreter should return if the block fails
# @yield
def exit_on_fail(message, code = 1)
yield
# First, we need to check and see if we are catching a SystemExit error. These will be raised
# when we daemonize/fork, and they do not necessarily indicate a failure case.
rescue SystemExit => err
raise err
# Now we need to catch *any* other kind of exception, because we may be calling third-party
# code (e.g. webrick), and we have no idea what they might throw.
rescue Exception => err
## NOTE: when debugging spec failures, these two lines can be very useful
#puts err.inspect
#puts Puppet::Util.pretty_backtrace(err.backtrace)
Puppet.log_exception(err, "Could not #{message}: #{err}")
Puppet::Util::Log.force_flushqueue()
exit(code)
end
module_function :exit_on_fail
#######################################################################################################
# Deprecated methods relating to process execution; these have been moved to Puppet::Util::Execution
#######################################################################################################
def execpipe(command, failonfail = true, &block)
Puppet.deprecation_warning("Puppet::Util.execpipe is deprecated; please use Puppet::Util::Execution.execpipe")
Puppet::Util::Execution.execpipe(command, failonfail, &block)
end
module_function :execpipe
def execfail(command, exception)
Puppet.deprecation_warning("Puppet::Util.execfail is deprecated; please use Puppet::Util::Execution.execfail")
Puppet::Util::Execution.execfail(command, exception)
end
module_function :execfail
def execute(command, arguments = {})
Puppet.deprecation_warning("Puppet::Util.execute is deprecated; please use Puppet::Util::Execution.execute")
Puppet::Util::Execution.execute(command, arguments)
end
module_function :execute
end
end
require 'puppet/util/errors'
require 'puppet/util/methodhelper'
require 'puppet/util/metaid'
require 'puppet/util/classgen'
require 'puppet/util/docs'
require 'puppet/util/execution'
require 'puppet/util/logging'
require 'puppet/util/package'
require 'puppet/util/warnings'
diff --git a/lib/puppet/util/colors.rb b/lib/puppet/util/colors.rb
index ab57fa2a6..5c74cc996 100644
--- a/lib/puppet/util/colors.rb
+++ b/lib/puppet/util/colors.rb
@@ -1,102 +1,127 @@
+require 'puppet/util/platform'
+
module Puppet::Util::Colors
BLACK = {:console => "\e[0;30m", :html => "color: #FFA0A0" }
RED = {:console => "\e[0;31m", :html => "color: #FFA0A0" }
GREEN = {:console => "\e[0;32m", :html => "color: #00CD00" }
YELLOW = {:console => "\e[0;33m", :html => "color: #FFFF60" }
BLUE = {:console => "\e[0;34m", :html => "color: #80A0FF" }
MAGENTA = {:console => "\e[0;35m", :html => "color: #FFA500" }
CYAN = {:console => "\e[0;36m", :html => "color: #40FFFF" }
WHITE = {:console => "\e[0;37m", :html => "color: #FFFFFF" }
HBLACK = {:console => "\e[1;30m", :html => "color: #FFA0A0" }
HRED = {:console => "\e[1;31m", :html => "color: #FFA0A0" }
HGREEN = {:console => "\e[1;32m", :html => "color: #00CD00" }
HYELLOW = {:console => "\e[1;33m", :html => "color: #FFFF60" }
HBLUE = {:console => "\e[1;34m", :html => "color: #80A0FF" }
HMAGENTA = {:console => "\e[1;35m", :html => "color: #FFA500" }
HCYAN = {:console => "\e[1;36m", :html => "color: #40FFFF" }
HWHITE = {:console => "\e[1;37m", :html => "color: #FFFFFF" }
BG_RED = {:console => "\e[0;41m", :html => "background: #FFA0A0"}
BG_GREEN = {:console => "\e[0;42m", :html => "background: #00CD00"}
BG_YELLOW = {:console => "\e[0;43m", :html => "background: #FFFF60"}
BG_BLUE = {:console => "\e[0;44m", :html => "background: #80A0FF"}
BG_MAGENTA = {:console => "\e[0;45m", :html => "background: #FFA500"}
BG_CYAN = {:console => "\e[0;46m", :html => "background: #40FFFF"}
BG_WHITE = {:console => "\e[0;47m", :html => "background: #FFFFFF"}
BG_HRED = {:console => "\e[1;41m", :html => "background: #FFA0A0"}
BG_HGREEN = {:console => "\e[1;42m", :html => "background: #00CD00"}
BG_HYELLOW = {:console => "\e[1;43m", :html => "background: #FFFF60"}
BG_HBLUE = {:console => "\e[1;44m", :html => "background: #80A0FF"}
BG_HMAGENTA = {:console => "\e[1;45m", :html => "background: #FFA500"}
BG_HCYAN = {:console => "\e[1;46m", :html => "background: #40FFFF"}
BG_HWHITE = {:console => "\e[1;47m", :html => "background: #FFFFFF"}
RESET = {:console => "\e[0m", :html => "" }
Colormap = {
:debug => WHITE,
:info => GREEN,
:notice => CYAN,
:warning => YELLOW,
:err => HMAGENTA,
:alert => RED,
:emerg => HRED,
:crit => HRED,
:black => BLACK,
:red => RED,
:green => GREEN,
:yellow => YELLOW,
:blue => BLUE,
:magenta => MAGENTA,
:cyan => CYAN,
:white => WHITE,
:hblack => HBLACK,
:hred => HRED,
:hgreen => HGREEN,
:hyellow => HYELLOW,
:hblue => HBLUE,
:hmagenta => HMAGENTA,
:hcyan => HCYAN,
:hwhite => HWHITE,
:bg_red => BG_RED,
:bg_green => BG_GREEN,
:bg_yellow => BG_YELLOW,
:bg_blue => BG_BLUE,
:bg_magenta => BG_MAGENTA,
:bg_cyan => BG_CYAN,
:bg_white => BG_WHITE,
:bg_hred => BG_HRED,
:bg_hgreen => BG_HGREEN,
:bg_hyellow => BG_HYELLOW,
:bg_hblue => BG_HBLUE,
:bg_hmagenta => BG_HMAGENTA,
:bg_hcyan => BG_HCYAN,
:bg_hwhite => BG_HWHITE,
:reset => { :console => "\e[m", :html => "" }
}
+ # We define console_has_color? at load time since it's checking the
+ # underlying platform which will not change, and we don't want to perform
+ # the check every time we use logging
+ if Puppet::Util::Platform.windows?
+ # We're on windows, need win32console for color to work
+ begin
+ require 'win32console'
+ rescue LoadError
+ def console_has_color?
+ false
+ end
+ else
+ def console_has_color?
+ true
+ end
+ end
+ else
+ # On a posix system we can just enable it
+ def console_has_color?
+ true
+ end
+ end
+
def colorize(color, str)
case Puppet[:color]
when true, :ansi, "ansi", "yes"
- if Puppet.features.ansicolor?
+ if console_has_color?
console_color(color, str)
else
str
end
when :html, "html"
html_color(color, str)
else
str
end
end
def console_color(color, str)
Colormap[color][:console] +
str.gsub(RESET[:console], Colormap[color][:console]) +
RESET[:console]
end
def html_color(color, str)
span = '' % Colormap[color][:html]
"#{span}%s" % str.gsub(//, "\\0#{span}")
end
end
diff --git a/lib/puppet/util/platform.rb b/lib/puppet/util/platform.rb
new file mode 100644
index 000000000..a6af448d9
--- /dev/null
+++ b/lib/puppet/util/platform.rb
@@ -0,0 +1,15 @@
+module Puppet
+ module Util
+ module Platform
+ def windows?
+ # Ruby only sets File::ALT_SEPARATOR on Windows and the Ruby standard
+ # library uses that to test what platform it's on. In some places we
+ # would use Puppet.features.microsoft_windows?, but this method can be
+ # used to determine the behavior of the underlying system without
+ # requiring features to be initialized and without side effect.
+ !!File::ALT_SEPARATOR
+ end
+ module_function :windows?
+ end
+ end
+end
diff --git a/spec/fixtures/unit/provider/package/gem/line-with-1.8.5-warning b/spec/fixtures/unit/provider/package/gem/line-with-1.8.5-warning
new file mode 100644
index 000000000..07a27e7b8
--- /dev/null
+++ b/spec/fixtures/unit/provider/package/gem/line-with-1.8.5-warning
@@ -0,0 +1,14 @@
+/home/jenkins/.rvm/gems/ruby-1.8.5-p231@global/gems/rubygems-bundler-0.9.0/lib/rubygems-bundler/regenerate_binstubs_command.rb:34: warning: parenthesize argument(s) for future version
+
+*** LOCAL GEMS ***
+
+columnize (0.3.2)
+diff-lcs (1.1.3)
+metaclass (0.0.1)
+mocha (0.10.5)
+rake (0.8.7)
+rspec-core (2.9.0)
+rspec-expectations (2.9.1)
+rspec-mocks (2.9.0)
+rubygems-bundler (0.9.0)
+rvm (1.11.3.3)
diff --git a/spec/unit/provider/package/gem_spec.rb b/spec/unit/provider/package/gem_spec.rb
index 516e57926..8e519d6f4 100755
--- a/spec/unit/provider/package/gem_spec.rb
+++ b/spec/unit/provider/package/gem_spec.rb
@@ -1,127 +1,143 @@
#!/usr/bin/env rspec
require 'spec_helper'
provider_class = Puppet::Type.type(:package).provider(:gem)
describe provider_class do
let(:resource) do
Puppet::Type.type(:package).new(
:name => 'myresource',
:ensure => :installed
)
end
let(:provider) do
provider = provider_class.new
provider.resource = resource
provider
end
describe "when installing" do
it "should use the path to the gem" do
provider_class.stubs(:command).with(:gemcmd).returns "/my/gem"
provider.expects(:execute).with { |args| args[0] == "/my/gem" }.returns ""
provider.install
end
it "should specify that the gem is being installed" do
provider.expects(:execute).with { |args| args[1] == "install" }.returns ""
provider.install
end
it "should specify that dependencies should be included" do
provider.expects(:execute).with { |args| args[2] == "--include-dependencies" }.returns ""
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[5] == "myresource" }.returns ""
provider.install
end
describe "when a source is specified" do
describe "as a normal file" do
it "should use the file name instead of the gem name" do
resource[:source] = "/my/file"
provider.expects(:execute).with { |args| args[3] == "/my/file" }.returns ""
provider.install
end
end
describe "as a file url" do
it "should use the file name instead of the gem name" do
resource[:source] = "file:///my/file"
provider.expects(:execute).with { |args| args[3] == "/my/file" }.returns ""
provider.install
end
end
describe "as a puppet url" do
it "should fail" do
resource[:source] = "puppet://my/file"
lambda { provider.install }.should raise_error(Puppet::Error)
end
end
describe "as a non-file and non-puppet url" do
it "should treat the source as a gem repository" do
resource[:source] = "http://host/my/file"
provider.expects(:execute).with { |args| args[3..5] == ["--source", "http://host/my/file", "myresource"] }.returns ""
provider.install
end
end
describe "with an invalid uri" do
it "should fail" do
URI.expects(:parse).raises(ArgumentError)
resource[:source] = "http:::::uppet:/:/my/file"
lambda { provider.install }.should raise_error(Puppet::Error)
end
end
end
end
describe "#latest" do
it "should return a single value for 'latest'" do
#gemlist is used for retrieving both local and remote version numbers, and there are cases
# (particularly local) where it makes sense for it to return an array. That doesn't make
# sense for '#latest', though.
provider.class.expects(:gemlist).with({ :justme => 'myresource'}).returns({
:name => 'myresource',
:ensure => ["3.0"],
:provider => :gem,
})
provider.latest.should == "3.0"
end
end
-
describe "#instances" do
before do
provider_class.stubs(:command).with(:gemcmd).returns "/my/gem"
end
it "should return an empty array when no gems installed" do
provider_class.expects(:execute).with(%w{/my/gem list --local}).returns("\n")
provider_class.instances.should == []
end
it "should return ensure values as an array of installed versions" do
provider_class.expects(:execute).with(%w{/my/gem list --local}).returns <<-HEREDOC.gsub(/ /, '')
systemu (1.2.0)
vagrant (0.8.7, 0.6.9)
HEREDOC
provider_class.instances.map {|p| p.properties}.should == [
{:ensure => ["1.2.0"], :provider => :gem, :name => 'systemu'},
{:ensure => ["0.8.7", "0.6.9"], :provider => :gem, :name => 'vagrant'}
]
end
+
+ it "should not fail when an unmatched line is returned" do
+ provider_class.expects(:execute).with(%w{/my/gem list --local}).
+ returns(File.read(my_fixture('line-with-1.8.5-warning')))
+
+ provider_class.instances.map {|p| p.properties}.
+ should == [{:provider=>:gem, :ensure=>["0.3.2"], :name=>"columnize"},
+ {:provider=>:gem, :ensure=>["1.1.3"], :name=>"diff-lcs"},
+ {:provider=>:gem, :ensure=>["0.0.1"], :name=>"metaclass"},
+ {:provider=>:gem, :ensure=>["0.10.5"], :name=>"mocha"},
+ {:provider=>:gem, :ensure=>["0.8.7"], :name=>"rake"},
+ {:provider=>:gem, :ensure=>["2.9.0"], :name=>"rspec-core"},
+ {:provider=>:gem, :ensure=>["2.9.1"], :name=>"rspec-expectations"},
+ {:provider=>:gem, :ensure=>["2.9.0"], :name=>"rspec-mocks"},
+ {:provider=>:gem, :ensure=>["0.9.0"], :name=>"rubygems-bundler"},
+ {:provider=>:gem, :ensure=>["1.11.3.3"], :name=>"rvm"}]
+ end
end
end
diff --git a/spec/unit/util/colors_spec.rb b/spec/unit/util/colors_spec.rb
index 86a062729..f114894da 100755
--- a/spec/unit/util/colors_spec.rb
+++ b/spec/unit/util/colors_spec.rb
@@ -1,69 +1,69 @@
#!/usr/bin/env ruby
require 'spec_helper'
describe Puppet::Util::Colors do
include Puppet::Util::Colors
let (:message) { 'a message' }
let (:color) { :black }
let (:subject) { self }
describe ".console_color" do
it { should respond_to :console_color }
it "should generate ANSI escape sequences" do
subject.console_color(color, message).should == "\e[0;30m#{message}\e[0m"
end
end
describe ".html_color" do
it { should respond_to :html_color }
it "should generate an HTML span element and style attribute" do
subject.html_color(color, message).should =~ /#{message}<\/span>/
end
end
describe ".colorize" do
it { should respond_to :colorize }
context "ansicolor supported" do
before :each do
- Puppet.features.stubs(:ansicolor?).returns(true)
+ subject.stubs(:console_has_color?).returns(true)
end
it "should colorize console output" do
Puppet[:color] = true
subject.expects(:console_color).with(color, message)
subject.colorize(:black, message)
end
it "should not colorize unknown color schemes" do
Puppet[:color] = :thisisanunknownscheme
subject.colorize(:black, message).should == message
end
end
context "ansicolor not supported" do
before :each do
- Puppet.features.stubs(:ansicolor?).returns(false)
+ subject.stubs(:console_has_color?).returns(false)
end
it "should not colorize console output" do
Puppet[:color] = true
subject.expects(:console_color).never
subject.colorize(:black, message).should == message
end
it "should colorize html output" do
Puppet[:color] = :html
subject.expects(:html_color).with(color, message)
subject.colorize(color, message)
end
end
end
end
diff --git a/spec/unit/util/log/destinations_spec.rb b/spec/unit/util/log/destinations_spec.rb
index b037e8449..b44b77762 100755
--- a/spec/unit/util/log/destinations_spec.rb
+++ b/spec/unit/util/log/destinations_spec.rb
@@ -1,166 +1,168 @@
#!/usr/bin/env ruby -S rspec
require 'spec_helper'
require 'puppet/util/log'
describe Puppet::Util::Log.desttypes[:report] do
before do
@dest = Puppet::Util::Log.desttypes[:report]
end
it "should require a report at initialization" do
@dest.new("foo").report.should == "foo"
end
it "should send new messages to the report" do
report = mock 'report'
dest = @dest.new(report)
report.expects(:<<).with("my log")
dest.handle "my log"
end
end
describe Puppet::Util::Log.desttypes[:file] do
include PuppetSpec::Files
before do
File.stubs(:open) # prevent actually creating the file
@class = Puppet::Util::Log.desttypes[:file]
end
it "should default to autoflush false" do
@class.new(tmpfile('log')).autoflush.should == true
end
describe "when matching" do
shared_examples_for "file destination" do
it "should match an absolute path" do
@class.match?(abspath).should be_true
end
it "should not match a relative path" do
@class.match?(relpath).should be_false
end
end
describe "on POSIX systems", :as_platform => :posix do
let (:abspath) { '/tmp/log' }
let (:relpath) { 'log' }
it_behaves_like "file destination"
end
describe "on Windows systems", :as_platform => :windows do
let (:abspath) { 'C:\\temp\\log.txt' }
let (:relpath) { 'log.txt' }
it_behaves_like "file destination"
end
end
end
describe Puppet::Util::Log.desttypes[:syslog] do
let (:klass) { Puppet::Util::Log.desttypes[:syslog] }
# these tests can only be run when syslog is present, because
# we can't stub the top-level Syslog module
describe "when syslog is available", :if => Puppet.features.syslog? do
before :each do
Syslog.stubs(:opened?).returns(false)
Syslog.stubs(:const_get).returns("LOG_KERN").returns(0)
Syslog.stubs(:open)
end
it "should open syslog" do
Syslog.expects(:open)
klass.new
end
it "should close syslog" do
Syslog.expects(:close)
dest = klass.new
dest.close
end
it "should send messages to syslog" do
syslog = mock 'syslog'
syslog.expects(:info).with("don't panic")
Syslog.stubs(:open).returns(syslog)
msg = Puppet::Util::Log.new(:level => :info, :message => "don't panic")
dest = klass.new
dest.handle(msg)
end
end
describe "when syslog is unavailable" do
it "should not be a suitable log destination" do
Puppet.features.stubs(:syslog?).returns(false)
klass.suitable?(:syslog).should be_false
end
end
end
describe Puppet::Util::Log.desttypes[:console] do
describe "when color is available" do
before :each do
- Puppet.features.stubs(:ansicolor?).returns(true)
+ subject.stubs(:console_has_color?).returns(true)
end
+
it "should support color output" do
Puppet[:color] = true
subject.colorize(:red, 'version').should == "\e[0;31mversion\e[0m"
end
it "should withhold color output when not appropriate" do
Puppet[:color] = false
subject.colorize(:red, 'version').should == "version"
end
it "should handle multiple overlapping colors in a stack-like way" do
Puppet[:color] = true
vstring = subject.colorize(:red, 'version')
subject.colorize(:green, "(#{vstring})").should == "\e[0;32m(\e[0;31mversion\e[0;32m)\e[0m"
end
it "should handle resets in a stack-like way" do
Puppet[:color] = true
vstring = subject.colorize(:reset, 'version')
subject.colorize(:green, "(#{vstring})").should == "\e[0;32m(\e[mversion\e[0;32m)\e[0m"
end
end
end
describe Puppet::Util::Log.desttypes[:telly_prototype_console] do
describe "when color is available" do
before :each do
- Puppet.features.stubs(:ansicolor?).returns(true)
+ subject.stubs(:console_has_color?).returns(true)
end
+
it "should support color output" do
Puppet[:color] = true
subject.colorize(:red, 'version').should == "\e[0;31mversion\e[0m"
end
it "should withhold color output when not appropriate" do
Puppet[:color] = false
subject.colorize(:red, 'version').should == "version"
end
it "should handle multiple overlapping colors in a stack-like way" do
Puppet[:color] = true
vstring = subject.colorize(:red, 'version')
subject.colorize(:green, "(#{vstring})").should == "\e[0;32m(\e[0;31mversion\e[0;32m)\e[0m"
end
it "should handle resets in a stack-like way" do
Puppet[:color] = true
vstring = subject.colorize(:reset, 'version')
subject.colorize(:green, "(#{vstring})").should == "\e[0;32m(\e[mversion\e[0;32m)\e[0m"
end
end
end