summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
authorDaniel Pittman <daniel@rimspace.net>2011-03-15 11:50:29 -0700
committerMatt Robinson <matt@puppetlabs.com>2011-03-15 11:55:59 -0700
commitfa0cfc60ccf43fcf9b4339b63b748b5f177c1e75 (patch)
tree4a0f1f649e7862c00fa7311a54b34d7a80b53a4f /lib
parentc86a980fe9b2f2e109fe7956a1be2705f3fc2ade (diff)
downloadpuppet-fa0cfc60ccf43fcf9b4339b63b748b5f177c1e75.tar.gz
puppet-fa0cfc60ccf43fcf9b4339b63b748b5f177c1e75.tar.xz
puppet-fa0cfc60ccf43fcf9b4339b63b748b5f177c1e75.zip
(#4884) Break the exec type out to have a posix provider
This is in preparation for allowing other new providers to handle exec commands differently. Reviewed-by: Max Martin and Matt Robinson
Diffstat (limited to 'lib')
-rw-r--r--lib/puppet/provider/exec/posix.rb112
-rwxr-xr-xlib/puppet/type/exec.rb179
2 files changed, 135 insertions, 156 deletions
diff --git a/lib/puppet/provider/exec/posix.rb b/lib/puppet/provider/exec/posix.rb
new file mode 100644
index 000000000..92dbd8c98
--- /dev/null
+++ b/lib/puppet/provider/exec/posix.rb
@@ -0,0 +1,112 @@
+Puppet::Type.type(:exec).provide :posix do
+ include Puppet::Util::Execution
+
+ confine :feature => :posix
+ defaultfor :feature => :posix
+
+ desc "Execute external binaries directly, on POSIX systems.
+This does not pass through a shell, or perform any interpolation, but
+only directly calls the command with the arguments given."
+
+ def run(command, check = false)
+ output = nil
+ status = nil
+ dir = nil
+
+ checkexe(command)
+
+ if dir = resource[:cwd]
+ unless File.directory?(dir)
+ if check
+ dir = nil
+ else
+ self.fail "Working directory '#{dir}' does not exist"
+ end
+ end
+ end
+
+ dir ||= Dir.pwd
+
+ debug "Executing#{check ? " check": ""} '#{command}'"
+ begin
+ # Do our chdir
+ Dir.chdir(dir) do
+ environment = {}
+
+ environment[:PATH] = resource[:path].join(":") if resource[:path]
+
+ if envlist = resource[:environment]
+ envlist = [envlist] unless envlist.is_a? Array
+ envlist.each do |setting|
+ if setting =~ /^(\w+)=((.|\n)+)$/
+ env_name = $1
+ value = $2
+ if environment.include?(env_name) || environment.include?(env_name.to_sym)
+ warning "Overriding environment setting '#{env_name}' with '#{value}'"
+ end
+ environment[env_name] = value
+ else
+ warning "Cannot understand environment setting #{setting.inspect}"
+ end
+ end
+ end
+
+ withenv environment do
+ Timeout::timeout(resource[:timeout]) do
+ output, status = Puppet::Util::SUIDManager.
+ run_and_capture([command], resource[:user], resource[:group])
+ end
+ # The shell returns 127 if the command is missing.
+ if status.exitstatus == 127
+ raise ArgumentError, output
+ end
+ end
+ end
+ rescue Errno::ENOENT => detail
+ self.fail detail.to_s
+ end
+
+ return output, status
+ end
+
+ # Verify that we have the executable
+ def checkexe(command)
+ exe = extractexe(command)
+
+ if resource[:path]
+ if Puppet.features.posix? and !File.exists?(exe)
+ withenv :PATH => resource[:path].join(File::PATH_SEPARATOR) do
+ exe = which(exe) || raise(ArgumentError,"Could not find command '#{exe}'")
+ end
+ elsif Puppet.features.microsoft_windows? and !File.exists?(exe)
+ resource[:path].each do |path|
+ [".exe", ".ps1", ".bat", ".com", ""].each do |extension|
+ file = File.join(path, exe+extension)
+ return if File.exists?(file)
+ end
+ end
+ end
+ end
+
+ raise ArgumentError, "Could not find command '#{exe}'" unless File.exists?(exe)
+ unless File.executable?(exe)
+ raise ArgumentError,
+ "'#{exe}' is not executable"
+ end
+ end
+
+ def extractexe(command)
+ # easy case: command was quoted
+ if command =~ /^"([^"]+)"/
+ $1
+ else
+ command.split(/ /)[0]
+ end
+ end
+
+ def validatecmd(command)
+ exe = extractexe(command)
+ # if we're not fully qualified, require a path
+ self.fail "'#{command}' is not qualified and no path was specified. Please qualify the command or specify a path." if File.expand_path(exe) != exe and resource[:path].nil?
+ end
+end
diff --git a/lib/puppet/type/exec.rb b/lib/puppet/type/exec.rb
index 387c592a6..67d40a7cc 100755
--- a/lib/puppet/type/exec.rb
+++ b/lib/puppet/type/exec.rb
@@ -28,10 +28,10 @@ module Puppet
# 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
@@ -63,9 +63,11 @@ module Puppet
# First verify that all of our checks pass.
def retrieve
- # Default to somethinng
-
- if @resource.check
+ # We need to return :notrun to trigger evaluation; when that isn't
+ # true, we *LIE* about what happened and return a "success" for the
+ # value, which causes us to be treated as in_sync?, which means we
+ # don't actually execute anything. I think. --daniel 2011-03-10
+ if @resource.check_all_attributes
return :notrun
else
return self.should
@@ -87,7 +89,7 @@ module Puppet
tries.times do |try|
# Only add debug messages for tries > 1 to reduce log spam.
debug("Exec try #{try+1}/#{tries}") if tries > 1
- @output, @status = @resource.run(self.resource[:command])
+ @output, @status = provider.run(self.resource[:command])
break if self.should.include?(@status.exitstatus.to_s)
if try_sleep > 0 and tries > 1
debug("Sleeping for #{try_sleep} seconds between tries")
@@ -137,7 +139,7 @@ module Puppet
newparam(:path) do
desc "The search path used for command execution.
Commands must be fully qualified if no path is specified. Paths
- can be specified as an array or as a colon-separated list."
+ can be specified as an array or as a colon or semi-colon separated list."
# Support both arrays and colon-separated fields.
def value=(*values)
@@ -174,21 +176,9 @@ module Puppet
# Validation is handled by the SUIDManager class.
end
- newparam(:cwd) do
+ newparam(:cwd, :parent => Puppet::Parameter::Path) do
desc "The directory from which to run the command. If
this directory does not exist, the command will fail."
-
- validate do |dir|
- unless dir =~ /^#{File::SEPARATOR}/
- self.fail("CWD must be a fully qualified path")
- end
- end
-
- munge do |dir|
- dir = dir[0] if dir.is_a?(Array)
-
- dir
- end
end
newparam(:logoutput) do
@@ -207,7 +197,7 @@ module Puppet
for refreshing."
validate do |command|
- @resource.validatecmd(command)
+ provider.validatecmd(command)
end
end
@@ -331,7 +321,7 @@ module Puppet
end
end
- newcheck(:creates) do
+ newcheck(:creates, :parent => Puppet::Parameter::Path) do
desc "A file that this command creates. If this
parameter is provided, then the command will only be run
if the specified file does not exist:
@@ -344,19 +334,7 @@ module Puppet
"
- # FIXME if they try to set this and fail, then we should probably
- # fail the entire exec, right?
- validate do |files|
- files = [files] unless files.is_a? Array
-
- files.each do |file|
- self.fail("'creates' must be set to a fully qualified path") unless file
-
- unless file =~ %r{^#{File::SEPARATOR}}
- self.fail "'creates' files must be fully qualified."
- end
- end
- end
+ accept_arrays
# If the file exists, return false (i.e., don't run the command),
# else return true
@@ -384,15 +362,15 @@ module Puppet
validate do |cmds|
cmds = [cmds] unless cmds.is_a? Array
- cmds.each do |cmd|
- @resource.validatecmd(cmd)
+ cmds.each do |command|
+ provider.validatecmd(command)
end
end
# Return true if the command does not return 0.
def check(value)
begin
- output, status = @resource.run(value, true)
+ output, status = provider.run(value, true)
rescue Timeout::Error
err "Check #{value.inspect} exceeded timeout"
return false
@@ -426,15 +404,15 @@ module Puppet
validate do |cmds|
cmds = [cmds] unless cmds.is_a? Array
- cmds.each do |cmd|
- @resource.validatecmd(cmd)
+ cmds.each do |command|
+ provider.validatecmd(command)
end
end
# Return true if the command returns 0.
def check(value)
begin
- output, status = @resource.run(value, true)
+ output, status = provider.run(value, true)
rescue Timeout::Error
err "Check #{value.inspect} exceeded timeout"
return false
@@ -448,7 +426,7 @@ module Puppet
@isomorphic = false
validate do
- validatecmd(self[:command])
+ provider.validatecmd(self[:command])
end
# FIXME exec should autorequire any exec that 'creates' our cwd
@@ -501,7 +479,7 @@ module Puppet
# Verify that we pass all of the checks. The argument determines whether
# we skip the :refreshonly check, which is necessary because we now check
# within refresh
- def check(refreshing = false)
+ def check_all_attributes(refreshing = false)
self.class.checks.each { |check|
next if refreshing and check == :refreshonly
if @parameters.include?(check)
@@ -516,32 +494,6 @@ module Puppet
true
end
- # Verify that we have the executable
- def checkexe(cmd)
- exe = extractexe(cmd)
-
- if self[:path]
- if Puppet.features.posix? and !File.exists?(exe)
- withenv :PATH => self[:path].join(File::PATH_SEPARATOR) do
- exe = which(exe) || raise(ArgumentError,"Could not find command '#{exe}'")
- end
- elsif Puppet.features.microsoft_windows? and !File.exists?(exe)
- self[:path].each do |path|
- [".exe", ".ps1", ".bat", ".com", ""].each do |extension|
- file = File.join(path, exe+extension)
- return if File.exists?(file)
- end
- end
- end
- end
-
- raise ArgumentError, "Could not find executable '#{exe}'" unless FileTest.exists?(exe)
- unless FileTest.executable?(exe)
- raise ArgumentError,
- "'#{exe}' is not executable"
- end
- end
-
def output
if self.property(:returns).nil?
return nil
@@ -552,98 +504,13 @@ module Puppet
# Run the command, or optionally run a separately-specified command.
def refresh
- if self.check(true)
+ if self.check_all_attributes(true)
if cmd = self[:refresh]
- self.run(cmd)
+ provider.run(cmd)
else
self.property(:returns).sync
end
end
end
-
- # Run a command.
- def run(command, check = false)
- output = nil
- status = nil
-
- dir = nil
-
- checkexe(command)
-
- if dir = self[:cwd]
- unless File.directory?(dir)
- if check
- dir = nil
- else
- self.fail "Working directory '#{dir}' does not exist"
- end
- end
- end
-
- dir ||= Dir.pwd
-
- if check
- debug "Executing check '#{command}'"
- else
- debug "Executing '#{command}'"
- end
- begin
- # Do our chdir
- Dir.chdir(dir) do
- environment = {}
-
- environment[:PATH] = self[:path].join(":") if self[:path]
-
- if envlist = self[:environment]
- envlist = [envlist] unless envlist.is_a? Array
- envlist.each do |setting|
- if setting =~ /^(\w+)=((.|\n)+)$/
- name = $1
- value = $2
- if environment.include? name
- warning(
- "Overriding environment setting '#{name}' with '#{value}'"
- )
- end
- environment[name] = value
- else
- warning "Cannot understand environment setting #{setting.inspect}"
- end
- end
- end
-
- withenv environment do
- Timeout::timeout(self[:timeout]) do
- output, status = Puppet::Util::SUIDManager.run_and_capture(
- [command], self[:user], self[:group]
- )
- end
- # The shell returns 127 if the command is missing.
- if status.exitstatus == 127
- raise ArgumentError, output
- end
- end
- end
- rescue Errno::ENOENT => detail
- self.fail detail.to_s
- end
-
- return output, status
- end
-
- def validatecmd(cmd)
- exe = extractexe(cmd)
- # if we're not fully qualified, require a path
- self.fail "'#{cmd}' is not qualified and no path was specified. Please qualify the command or specify a path." if File.expand_path(exe) != exe and self[:path].nil?
- end
-
- def extractexe(cmd)
- # easy case: command was quoted
- if cmd =~ /^"([^"]+)"/
- $1
- else
- cmd.split(/ /)[0]
- end
- end
end
end