summaryrefslogtreecommitdiffstats
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/puppet.rb5
-rw-r--r--lib/puppet/application.rb24
-rw-r--r--lib/puppet/application/agent.rb6
-rw-r--r--lib/puppet/application/apply.rb3
-rw-r--r--lib/puppet/application/master.rb2
-rw-r--r--lib/puppet/configurer.rb11
-rw-r--r--lib/puppet/defaults.rb9
-rw-r--r--lib/puppet/file_serving/fileset.rb1
-rw-r--r--lib/puppet/indirector/exec.rb9
-rw-r--r--lib/puppet/indirector/queue.rb2
-rw-r--r--lib/puppet/indirector/report/yaml.rb11
-rw-r--r--lib/puppet/node/environment.rb2
-rw-r--r--lib/puppet/parameter.rb2
-rw-r--r--lib/puppet/parameter/path.rb42
-rw-r--r--lib/puppet/parser/compiler.rb35
-rw-r--r--lib/puppet/parser/lexer.rb5
-rw-r--r--lib/puppet/parser/parser_support.rb1
-rw-r--r--lib/puppet/parser/resource.rb19
-rw-r--r--lib/puppet/parser/scope.rb4
-rw-r--r--lib/puppet/provider/exec/posix.rb112
-rw-r--r--lib/puppet/provider/exec/shell.rb17
-rw-r--r--lib/puppet/provider/group/groupadd.rb3
-rwxr-xr-xlib/puppet/provider/mount/parsed.rb2
-rw-r--r--lib/puppet/provider/naginator.rb10
-rw-r--r--lib/puppet/provider/nameservice/directoryservice.rb19
-rwxr-xr-xlib/puppet/provider/package/aptitude.rb1
-rwxr-xr-xlib/puppet/provider/package/darwinport.rb86
-rwxr-xr-xlib/puppet/provider/package/gem.rb4
-rwxr-xr-xlib/puppet/provider/package/macports.rb106
-rwxr-xr-xlib/puppet/provider/service/debian.rb8
-rw-r--r--lib/puppet/rails/host.rb7
-rw-r--r--lib/puppet/rails/inventory_node.rb5
-rw-r--r--lib/puppet/resource/type_collection.rb7
-rw-r--r--lib/puppet/simple_graph.rb2
-rw-r--r--lib/puppet/transaction.rb2
-rw-r--r--lib/puppet/transaction/report.rb38
-rwxr-xr-xlib/puppet/type/cron.rb4
-rwxr-xr-xlib/puppet/type/exec.rb197
-rw-r--r--lib/puppet/type/file.rb13
-rwxr-xr-xlib/puppet/type/file/source.rb1
-rwxr-xr-xlib/puppet/type/group.rb12
-rw-r--r--lib/puppet/type/service.rb30
-rw-r--r--lib/puppet/util/command_line.rb27
-rwxr-xr-xlib/puppet/util/command_line/puppetrun1
-rwxr-xr-xlib/puppet/util/loadedfile.rb6
-rw-r--r--lib/puppet/util/metric.rb8
-rw-r--r--lib/puppet/util/plugins.rb82
-rw-r--r--lib/puppet/util/queue.rb2
-rw-r--r--lib/puppet/util/queue/stomp.rb4
49 files changed, 644 insertions, 365 deletions
diff --git a/lib/puppet.rb b/lib/puppet.rb
index ef5fb7f06..f97d28642 100644
--- a/lib/puppet.rb
+++ b/lib/puppet.rb
@@ -24,7 +24,7 @@ require 'puppet/util/run_mode'
# it's also a place to find top-level commands like 'debug'
module Puppet
- PUPPETVERSION = '2.6.7'
+ PUPPETVERSION = '2.6.8'
def Puppet.version
PUPPETVERSION
@@ -59,8 +59,7 @@ module Puppet
# configuration parameter access and stuff
def self.[](param)
- case param
- when :debug
+ if param == :debug
return Puppet::Util::Log.level == :debug
else
return @@settings[param]
diff --git a/lib/puppet/application.rb b/lib/puppet/application.rb
index c3d7355f6..a028a158f 100644
--- a/lib/puppet/application.rb
+++ b/lib/puppet/application.rb
@@ -1,4 +1,5 @@
require 'optparse'
+require 'puppet/util/plugins'
# This class handles all the aspects of a Puppet application/executable
# * setting up options
@@ -297,11 +298,11 @@ class Application
# This is the main application entry point
def run
- exit_on_fail("initialize") { preinit }
- exit_on_fail("parse options") { parse_options }
+ exit_on_fail("initialize") { hook('preinit') { preinit } }
+ exit_on_fail("parse options") { hook('parse_options') { parse_options } }
exit_on_fail("parse configuration file") { Puppet.settings.parse } if should_parse_config?
- exit_on_fail("prepare for execution") { setup }
- exit_on_fail("run") { run_command }
+ exit_on_fail("prepare for execution") { hook('setup') { setup } }
+ exit_on_fail("run") { hook('run_command') { run_command } }
end
def main
@@ -407,11 +408,18 @@ class Application
private
def exit_on_fail(message, code = 1)
- yield
+ yield
rescue RuntimeError, NotImplementedError => detail
- puts detail.backtrace if Puppet[:trace]
- $stderr.puts "Could not #{message}: #{detail}"
- exit(code)
+ puts detail.backtrace if Puppet[:trace]
+ $stderr.puts "Could not #{message}: #{detail}"
+ exit(code)
+ end
+
+ def hook(step,&block)
+ Puppet::Plugins.send("before_application_#{step}",:application_object => self)
+ x = yield
+ Puppet::Plugins.send("after_application_#{step}",:application_object => self, :return_value => x)
+ x
end
end
end
diff --git a/lib/puppet/application/agent.rb b/lib/puppet/application/agent.rb
index 53e083232..cf51f0825 100644
--- a/lib/puppet/application/agent.rb
+++ b/lib/puppet/application/agent.rb
@@ -168,8 +168,8 @@ class Puppet::Application::Agent < Puppet::Application
end
def setup_listen
- unless FileTest.exists?(Puppet[:authconfig])
- Puppet.err "Will not start without authorization file #{Puppet[:authconfig]}"
+ unless FileTest.exists?(Puppet[:rest_authconfig])
+ Puppet.err "Will not start without authorization file #{Puppet[:rest_authconfig]}"
exit(14)
end
@@ -229,6 +229,8 @@ class Puppet::Application::Agent < Puppet::Application
Puppet::SSL::Host.ca_location = options[:fingerprint] ? :none : :remote
Puppet::Transaction::Report.terminus_class = :rest
+ # we want the last report to be persisted locally
+ Puppet::Transaction::Report.cache_class = :yaml
# Override the default; puppetd needs this, usually.
# You can still override this on the command-line with, e.g., :compiler.
diff --git a/lib/puppet/application/apply.rb b/lib/puppet/application/apply.rb
index cc733e1f5..7f0b95a89 100644
--- a/lib/puppet/application/apply.rb
+++ b/lib/puppet/application/apply.rb
@@ -148,6 +148,9 @@ class Puppet::Application::Apply < Puppet::Application
exit(1)
end
+ # we want the last report to be persisted locally
+ Puppet::Transaction::Report.cache_class = :yaml
+
if options[:debug]
Puppet::Util::Log.level = :debug
elsif options[:verbose]
diff --git a/lib/puppet/application/master.rb b/lib/puppet/application/master.rb
index 6d1cdef1b..c5e9ada54 100644
--- a/lib/puppet/application/master.rb
+++ b/lib/puppet/application/master.rb
@@ -136,7 +136,7 @@ class Puppet::Application::Master < Puppet::Application
exit(Puppet.settings.print_configs ? 0 : 1) if Puppet.settings.print_configs?
- Puppet.settings.use :main, :master, :ssl
+ Puppet.settings.use :main, :master, :ssl, :metrics
# Cache our nodes in yaml. Currently not configurable.
Puppet::Node.cache_class = :yaml
diff --git a/lib/puppet/configurer.rb b/lib/puppet/configurer.rb
index d3c902576..9f68ca499 100644
--- a/lib/puppet/configurer.rb
+++ b/lib/puppet/configurer.rb
@@ -166,19 +166,28 @@ class Puppet::Configurer
execute_postrun_command
Puppet::Util::Log.close(report)
-
send_report(report, transaction)
end
def send_report(report, trans)
report.finalize_report if trans
puts report.summary if Puppet[:summarize]
+ save_last_run_summary(report)
report.save if Puppet[:report]
rescue => detail
puts detail.backtrace if Puppet[:trace]
Puppet.err "Could not send report: #{detail}"
end
+ def save_last_run_summary(report)
+ Puppet::Util::FileLocking.writelock(Puppet[:lastrunfile], 0660) do |file|
+ file.print YAML.dump(report.raw_summary)
+ end
+ rescue => detail
+ puts detail.backtrace if Puppet[:trace]
+ Puppet.err "Could not save last run local report: #{detail}"
+ end
+
private
def self.timeout
diff --git a/lib/puppet/defaults.rb b/lib/puppet/defaults.rb
index 8da104086..2a1ded592 100644
--- a/lib/puppet/defaults.rb
+++ b/lib/puppet/defaults.rb
@@ -474,6 +474,7 @@ module Puppet
setdefaults(:metrics,
:rrddir => {:default => "$vardir/rrd",
+ :mode => 0750,
:owner => "service",
:group => "service",
:desc => "The directory where RRD database files are stored.
@@ -606,6 +607,14 @@ module Puppet
:report => [false,
"Whether to send reports after every transaction."
],
+ :lastrunfile => { :default => "$statedir/last_run_summary.yaml",
+ :mode => 0660,
+ :desc => "Where puppet agent stores the last run report summary in yaml format."
+ },
+ :lastrunreport => { :default => "$statedir/last_run_report.yaml",
+ :mode => 0660,
+ :desc => "Where puppet agent stores the last run report in yaml format."
+ },
:graph => [false, "Whether to create dot graph files for the different
configuration graphs. These dot files can be interpreted by tools
like OmniGraffle or dot (which is part of ImageMagick)."],
diff --git a/lib/puppet/file_serving/fileset.rb b/lib/puppet/file_serving/fileset.rb
index fdbcf93a3..f29f70a53 100644
--- a/lib/puppet/file_serving/fileset.rb
+++ b/lib/puppet/file_serving/fileset.rb
@@ -59,6 +59,7 @@ class Puppet::FileServing::Fileset
end
def initialize(path, options = {})
+ path = path.chomp(File::SEPARATOR) unless path == File::SEPARATOR
raise ArgumentError.new("Fileset paths must be fully qualified") unless File.expand_path(path) == path
@path = path
diff --git a/lib/puppet/indirector/exec.rb b/lib/puppet/indirector/exec.rb
index fa789442b..8ea13ff95 100644
--- a/lib/puppet/indirector/exec.rb
+++ b/lib/puppet/indirector/exec.rb
@@ -16,8 +16,8 @@ class Puppet::Indirector::Exec < Puppet::Indirector::Terminus
private
# Proxy the execution, so it's easier to test.
- def execute(command)
- Puppet::Util.execute(command)
+ def execute(command, arguments)
+ Puppet::Util.execute(command,arguments)
end
# Call the external command and see if it returns our output.
@@ -33,10 +33,9 @@ class Puppet::Indirector::Exec < Puppet::Indirector::Terminus
# Add our name to it.
external_command << name
begin
- output = execute(external_command)
+ output = execute(external_command, :combine => false)
rescue Puppet::ExecutionFailure => detail
- Puppet.err "Failed to find #{name} via exec: #{detail}"
- return nil
+ raise Puppet::Error, "Failed to find #{name} via exec: #{detail}"
end
if output =~ /\A\s*\Z/ # all whitespace
diff --git a/lib/puppet/indirector/queue.rb b/lib/puppet/indirector/queue.rb
index fd089f431..85ffacacc 100644
--- a/lib/puppet/indirector/queue.rb
+++ b/lib/puppet/indirector/queue.rb
@@ -36,7 +36,7 @@ class Puppet::Indirector::Queue < Puppet::Indirector::Terminus
def save(request)
result = nil
benchmark :info, "Queued #{indirection.name} for #{request.key}" do
- result = client.send_message(queue, request.instance.render(:pson))
+ result = client.publish_message(queue, request.instance.render(:pson))
end
result
rescue => detail
diff --git a/lib/puppet/indirector/report/yaml.rb b/lib/puppet/indirector/report/yaml.rb
new file mode 100644
index 000000000..bf7bf4fe5
--- /dev/null
+++ b/lib/puppet/indirector/report/yaml.rb
@@ -0,0 +1,11 @@
+require 'puppet/transaction/report'
+require 'puppet/indirector/yaml'
+
+class Puppet::Transaction::Report::Yaml < Puppet::Indirector::Yaml
+ desc "Store last report as a flat file, serialized using YAML."
+
+ # Force report to be saved there
+ def path(name,ext='.yaml')
+ Puppet[:lastrunreport]
+ end
+end
diff --git a/lib/puppet/node/environment.rb b/lib/puppet/node/environment.rb
index b64fb8a1f..807479bc8 100644
--- a/lib/puppet/node/environment.rb
+++ b/lib/puppet/node/environment.rb
@@ -79,7 +79,7 @@ class Puppet::Node::Environment
# environment has changed do we delve deeper.
Thread.current[:known_resource_types] = nil if (krt = Thread.current[:known_resource_types]) && krt.environment != self
Thread.current[:known_resource_types] ||= synchronize {
- if @known_resource_types.nil? or @known_resource_types.stale?
+ if @known_resource_types.nil? or @known_resource_types.require_reparse?
@known_resource_types = Puppet::Resource::TypeCollection.new(self)
@known_resource_types.perform_initial_import
end
diff --git a/lib/puppet/parameter.rb b/lib/puppet/parameter.rb
index ff7cab22b..29d60fc66 100644
--- a/lib/puppet/parameter.rb
+++ b/lib/puppet/parameter.rb
@@ -300,3 +300,5 @@ class Puppet::Parameter
name.to_s
end
end
+
+require 'puppet/parameter/path'
diff --git a/lib/puppet/parameter/path.rb b/lib/puppet/parameter/path.rb
new file mode 100644
index 000000000..44886afd0
--- /dev/null
+++ b/lib/puppet/parameter/path.rb
@@ -0,0 +1,42 @@
+require 'puppet/parameter'
+
+class Puppet::Parameter::Path < Puppet::Parameter
+ def self.accept_arrays(bool = true)
+ @accept_arrays = !!bool
+ end
+ def self.arrays?
+ @accept_arrays
+ end
+
+ def validate_path(paths)
+ if paths.is_a?(Array) and ! self.class.arrays? then
+ fail "#{name} only accepts a single path, not an array of paths"
+ end
+
+ # We *always* support Unix path separators, as Win32 does now too.
+ absolute = "[/#{::Regexp.quote(::File::SEPARATOR)}]"
+ win32 = Puppet.features.microsoft_windows?
+
+ Array(paths).each do |path|
+ next if path =~ %r{^#{absolute}}
+ next if win32 and path =~ %r{^(?:[a-zA-Z]:)?#{absolute}}
+ fail("#{name} must be a fully qualified path")
+ end
+
+ paths
+ end
+
+ # This will be overridden if someone uses the validate option, which is why
+ # it just delegates to the other, useful, method.
+ def unsafe_validate(paths)
+ validate_path(paths)
+ end
+
+ # Likewise, this might be overridden, but by default...
+ def unsafe_munge(paths)
+ if paths.is_a?(Array) and ! self.class.arrays? then
+ fail "#{name} only accepts a single path, not an array of paths"
+ end
+ paths
+ end
+end
diff --git a/lib/puppet/parser/compiler.rb b/lib/puppet/parser/compiler.rb
index fdabd05c9..2d065dad5 100644
--- a/lib/puppet/parser/compiler.rb
+++ b/lib/puppet/parser/compiler.rb
@@ -15,15 +15,19 @@ class Puppet::Parser::Compiler
include Puppet::Resource::TypeCollectionHelper
def self.compile(node)
+ # We get these from the environment and only cache them in a thread
+ # variable for the duration of the compilation. If nothing else is using
+ # the thread, though, we can leave 'em hanging round with no ill effects,
+ # and this is safer than cleaning them at the end and assuming that will
+ # stick until the next entry to this function.
+ Thread.current[:known_resource_types] = nil
+ Thread.current[:env_module_directories] = nil
+
+ # ...and we actually do the compile now we have caching ready.
new(node).compile.to_resource
rescue => detail
puts detail.backtrace if Puppet[:trace]
raise Puppet::Error, "#{detail} on node #{node.name}"
- ensure
- # We get these from the environment and only cache them in a thread
- # variable for the duration of the compilation.
- Thread.current[:known_resource_types] = nil
- Thread.current[:env_module_directories] = nil
end
attr_reader :node, :facts, :collections, :catalog, :node_scope, :resources, :relationships
@@ -56,23 +60,20 @@ class Puppet::Parser::Compiler
# Note that this will fail if the resource is not unique.
@catalog.add_resource(resource)
+ if resource.type.to_s.downcase != "class" && resource[:stage]
+ raise ArgumentError, "Only classes can set 'stage'; normal resources like #{resource} cannot change run stage"
+ end
- # Add our container edge. If we're a class, then we get treated specially - we can
- # control the stage that the class is applied in. Otherwise, we just
- # get added to our parent container.
+ # Stages should not be inside of classes. They are always a
+ # top-level container, regardless of where they appear in the
+ # manifest.
return if resource.type.to_s.downcase == "stage"
+ # This adds a resource to the class it lexically appears in in the
+ # manifest.
if resource.type.to_s.downcase != "class"
- raise ArgumentError, "Only classes can set 'stage'; normal resources like #{resource} cannot change run stage" if resource[:stage]
return @catalog.add_edge(scope.resource, resource)
end
-
- unless stage = @catalog.resource(:stage, resource[:stage] || (scope && scope.resource && scope.resource[:stage]) || :main)
- raise ArgumentError, "Could not find stage #{resource[:stage] || :main} specified by #{resource}"
- end
-
- resource[:stage] ||= stage.title unless stage.title == :main
- @catalog.add_edge(stage, resource)
end
# Do we use nodes found in the code, vs. the external node sources?
@@ -162,7 +163,7 @@ class Puppet::Parser::Compiler
resource.evaluate unless lazy_evaluate
found << name
else
- Puppet.info "Could not find class #{name} for #{node.name}"
+ Puppet.warning "Could not find class #{name} for #{node.name}"
@catalog.tag(name)
end
end
diff --git a/lib/puppet/parser/lexer.rb b/lib/puppet/parser/lexer.rb
index 9a25263f6..71d9440ff 100644
--- a/lib/puppet/parser/lexer.rb
+++ b/lib/puppet/parser/lexer.rb
@@ -312,7 +312,8 @@ class Puppet::Parser::Lexer
def file=(file)
@file = file
@line = 1
- @scanner = StringScanner.new(File.read(file))
+ contents = File.exists?(file) ? File.read(file) : ""
+ @scanner = StringScanner.new(contents)
end
def shift_token
@@ -547,7 +548,7 @@ class Puppet::Parser::Lexer
value,terminator = slurpstring('"$')
token_queue << [TOKENS[token_type[terminator]],preamble+value]
if terminator != '$' or @scanner.scan(/\{/)
- token_queue.shift
+ token_queue.shift
elsif var_name = @scanner.scan(%r{(\w*::)*\w+|[0-9]})
token_queue << [TOKENS[:VARIABLE],var_name]
tokenize_interpolated_string(DQ_continuation_token_types)
diff --git a/lib/puppet/parser/parser_support.rb b/lib/puppet/parser/parser_support.rb
index 7a0aa2601..9e580efb2 100644
--- a/lib/puppet/parser/parser_support.rb
+++ b/lib/puppet/parser/parser_support.rb
@@ -88,7 +88,6 @@ class Puppet::Parser::Parser
unless file =~ /\.pp$/
file = file + ".pp"
end
- raise Puppet::Error, "Could not find file #{file}" unless FileTest.exist?(file)
end
raise Puppet::AlreadyImportedError, "Import loop detected" if known_resource_types.watching_file?(file)
diff --git a/lib/puppet/parser/resource.rb b/lib/puppet/parser/resource.rb
index c007d4dbe..e4f913013 100644
--- a/lib/puppet/parser/resource.rb
+++ b/lib/puppet/parser/resource.rb
@@ -62,13 +62,30 @@ class Puppet::Parser::Resource < Puppet::Resource
scope.environment
end
+ # Process the stage metaparameter for a class. A containment edge
+ # is drawn from the class to the stage. The stage for containment
+ # defaults to main, if none is specified.
+ def add_edge_to_stage
+ return unless self.type.to_s.downcase == "class"
+
+ unless stage = catalog.resource(:stage, self[:stage] || (scope && scope.resource && scope.resource[:stage]) || :main)
+ raise ArgumentError, "Could not find stage #{self[:stage] || :main} specified by #{self}"
+ end
+
+ self[:stage] ||= stage.title unless stage.title == :main
+ catalog.add_edge(stage, self)
+ end
+
# Retrieve the associated definition and evaluate it.
def evaluate
return if evaluated?
@evaluated = true
if klass = resource_type and ! builtin_type?
finish
- return klass.evaluate_code(self)
+ evaluated_code = klass.evaluate_code(self)
+ add_edge_to_stage
+
+ return evaluated_code
elsif builtin?
devfail "Cannot evaluate a builtin type (#{type})"
else
diff --git a/lib/puppet/parser/scope.rb b/lib/puppet/parser/scope.rb
index 24f1d01f7..c369f129a 100644
--- a/lib/puppet/parser/scope.rb
+++ b/lib/puppet/parser/scope.rb
@@ -101,7 +101,7 @@ class Puppet::Parser::Scope
# Remove this when rebasing
def environment
- compiler.environment
+ compiler ? compiler.environment : nil
end
# Are we the top scope?
@@ -513,6 +513,6 @@ class Puppet::Parser::Scope
def extend_with_functions_module
extend Puppet::Parser::Functions.environment_module(Puppet::Node::Environment.root)
- extend Puppet::Parser::Functions.environment_module(compiler ? environment : nil)
+ extend Puppet::Parser::Functions.environment_module(environment)
end
end
diff --git a/lib/puppet/provider/exec/posix.rb b/lib/puppet/provider/exec/posix.rb
new file mode 100644
index 000000000..92dbd8c98
--- /dev/null
+++ b/lib/puppet/provider/exec/posix.rb
@@ -0,0 +1,112 @@
+Puppet::Type.type(:exec).provide :posix do
+ include Puppet::Util::Execution
+
+ confine :feature => :posix
+ defaultfor :feature => :posix
+
+ desc "Execute external binaries directly, on POSIX systems.
+This does not pass through a shell, or perform any interpolation, but
+only directly calls the command with the arguments given."
+
+ def run(command, check = false)
+ output = nil
+ status = nil
+ dir = nil
+
+ checkexe(command)
+
+ if dir = resource[:cwd]
+ unless File.directory?(dir)
+ if check
+ dir = nil
+ else
+ self.fail "Working directory '#{dir}' does not exist"
+ end
+ end
+ end
+
+ dir ||= Dir.pwd
+
+ debug "Executing#{check ? " check": ""} '#{command}'"
+ begin
+ # Do our chdir
+ Dir.chdir(dir) do
+ environment = {}
+
+ environment[:PATH] = resource[:path].join(":") if resource[:path]
+
+ if envlist = resource[:environment]
+ envlist = [envlist] unless envlist.is_a? Array
+ envlist.each do |setting|
+ if setting =~ /^(\w+)=((.|\n)+)$/
+ env_name = $1
+ value = $2
+ if environment.include?(env_name) || environment.include?(env_name.to_sym)
+ warning "Overriding environment setting '#{env_name}' with '#{value}'"
+ end
+ environment[env_name] = value
+ else
+ warning "Cannot understand environment setting #{setting.inspect}"
+ end
+ end
+ end
+
+ withenv environment do
+ Timeout::timeout(resource[:timeout]) do
+ output, status = Puppet::Util::SUIDManager.
+ run_and_capture([command], resource[:user], resource[:group])
+ end
+ # The shell returns 127 if the command is missing.
+ if status.exitstatus == 127
+ raise ArgumentError, output
+ end
+ end
+ end
+ rescue Errno::ENOENT => detail
+ self.fail detail.to_s
+ end
+
+ return output, status
+ end
+
+ # Verify that we have the executable
+ def checkexe(command)
+ exe = extractexe(command)
+
+ if resource[:path]
+ if Puppet.features.posix? and !File.exists?(exe)
+ withenv :PATH => resource[:path].join(File::PATH_SEPARATOR) do
+ exe = which(exe) || raise(ArgumentError,"Could not find command '#{exe}'")
+ end
+ elsif Puppet.features.microsoft_windows? and !File.exists?(exe)
+ resource[:path].each do |path|
+ [".exe", ".ps1", ".bat", ".com", ""].each do |extension|
+ file = File.join(path, exe+extension)
+ return if File.exists?(file)
+ end
+ end
+ end
+ end
+
+ raise ArgumentError, "Could not find command '#{exe}'" unless File.exists?(exe)
+ unless File.executable?(exe)
+ raise ArgumentError,
+ "'#{exe}' is not executable"
+ end
+ end
+
+ def extractexe(command)
+ # easy case: command was quoted
+ if command =~ /^"([^"]+)"/
+ $1
+ else
+ command.split(/ /)[0]
+ end
+ end
+
+ def validatecmd(command)
+ exe = extractexe(command)
+ # if we're not fully qualified, require a path
+ self.fail "'#{command}' is not qualified and no path was specified. Please qualify the command or specify a path." if File.expand_path(exe) != exe and resource[:path].nil?
+ end
+end
diff --git a/lib/puppet/provider/exec/shell.rb b/lib/puppet/provider/exec/shell.rb
new file mode 100644
index 000000000..98f309e8f
--- /dev/null
+++ b/lib/puppet/provider/exec/shell.rb
@@ -0,0 +1,17 @@
+Puppet::Type.type(:exec).provide :shell, :parent => :posix do
+ include Puppet::Util::Execution
+
+ confine :feature => :posix
+
+ desc "Execute external binaries directly, on POSIX systems.
+passing through a shell so that shell built ins are available."
+
+ def run(command, check = false)
+ command = %Q{/bin/sh -c "#{command.gsub(/"/,'\"')}"}
+ super(command, check)
+ end
+
+ def validatecmd(command)
+ true
+ end
+end
diff --git a/lib/puppet/provider/group/groupadd.rb b/lib/puppet/provider/group/groupadd.rb
index 82ed4c0c7..bcc08d9f7 100644
--- a/lib/puppet/provider/group/groupadd.rb
+++ b/lib/puppet/provider/group/groupadd.rb
@@ -9,6 +9,8 @@ Puppet::Type.type(:group).provide :groupadd, :parent => Puppet::Provider::NameSe
commands :add => "groupadd", :delete => "groupdel", :modify => "groupmod"
+ has_feature :system_groups
+
verify :gid, "GID must be an integer" do |value|
value.is_a? Integer
end
@@ -21,6 +23,7 @@ Puppet::Type.type(:group).provide :groupadd, :parent => Puppet::Provider::NameSe
end
end
cmd << "-o" if @resource.allowdupe?
+ cmd << "-r" if @resource.system?
cmd << @resource[:name]
cmd
diff --git a/lib/puppet/provider/mount/parsed.rb b/lib/puppet/provider/mount/parsed.rb
index 11c5e21a9..7c3f41bbd 100755
--- a/lib/puppet/provider/mount/parsed.rb
+++ b/lib/puppet/provider/mount/parsed.rb
@@ -18,7 +18,7 @@ Puppet::Type.type(:mount).provide(
commands :mountcmd => "mount", :umount => "umount"
- case Facter["operatingsystem"]
+ case Facter.value(:operatingsystem)
when "Solaris"
@fields = [:device, :blockdevice, :name, :fstype, :pass, :atboot, :options]
else
diff --git a/lib/puppet/provider/naginator.rb b/lib/puppet/provider/naginator.rb
index 5c610fb31..17cc24086 100644
--- a/lib/puppet/provider/naginator.rb
+++ b/lib/puppet/provider/naginator.rb
@@ -30,7 +30,15 @@ class Puppet::Provider::Naginator < Puppet::Provider::ParsedFile
end
def self.to_file(records)
- header + records.collect { |record| record.to_s }.join("\n").gsub("_naginator_name", NAME_STRING)
+ header + records.collect { |record|
+ # Remap the TYPE_name or _naginator_name params to the
+ # name if the record is a template (register == 0)
+ if record.to_s =~ /register\s+0/
+ record.to_s.sub("_naginator_name", "name").sub(record.type.to_s + "_name", "name")
+ else
+ record.to_s.sub("_naginator_name", NAME_STRING)
+ end
+ }.join("\n")
end
def self.skip_record?(record)
diff --git a/lib/puppet/provider/nameservice/directoryservice.rb b/lib/puppet/provider/nameservice/directoryservice.rb
index b01880360..aab491122 100644
--- a/lib/puppet/provider/nameservice/directoryservice.rb
+++ b/lib/puppet/provider/nameservice/directoryservice.rb
@@ -235,11 +235,12 @@ class DirectoryService < Puppet::Provider::NameService
# have a lot of choice. Ultimately this should all be done using Ruby
# to access the DirectoryService APIs directly, but that's simply not
# feasible for a while yet.
- case self.get_macosx_version_major
- when "10.4"
- dscl_plist = self.parse_dscl_url_data(dscl_output)
- when "10.5", "10.6"
+ if self.get_macosx_version_major > "10.4"
dscl_plist = self.parse_dscl_plist_data(dscl_output)
+ elsif self.get_macosx_version_major == "10.4"
+ dscl_plist = self.parse_dscl_url_data(dscl_output)
+ else
+ fail("Puppet does not support OS X versions < 10.4")
end
self.generate_attribute_hash(dscl_plist, *type_properties)
@@ -257,12 +258,14 @@ class DirectoryService < Puppet::Provider::NameService
# different format for the -url output with objects with spaces in
# their values. *sigh*. Use -url for 10.4 in the hope this can be
# deprecated one day, and use -plist for 10.5 and higher.
- case self.get_macosx_version_major
- when "10.4"
- command_vector = [ command(:dscl), "-url", "." ]
- when "10.5", "10.6"
+ if self.get_macosx_version_major > "10.4"
command_vector = [ command(:dscl), "-plist", "." ]
+ elsif self.get_macosx_version_major == "10.4"
+ command_vector = [ command(:dscl), "-url", "." ]
+ else
+ fail("Puppet does not support OS X versions < 10.4")
end
+
# JJM: The actual action to perform. See "man dscl"
# Common actiosn: -create, -delete, -merge, -append, -passwd
command_vector << ds_action
diff --git a/lib/puppet/provider/package/aptitude.rb b/lib/puppet/provider/package/aptitude.rb
index 8bdf984e6..2eafd3ef8 100755
--- a/lib/puppet/provider/package/aptitude.rb
+++ b/lib/puppet/provider/package/aptitude.rb
@@ -12,6 +12,7 @@ Puppet::Type.type(:package).provide :aptitude, :parent => :apt, :source => :dpkg
args.flatten!
# Apparently aptitude hasn't always supported a -q flag.
args.delete("-q") if args.include?("-q")
+ args.delete("--force-yes") if args.include?("--force-yes")
output = aptitude(*args)
# Yay, stupid aptitude doesn't throw an error when the package is missing.
diff --git a/lib/puppet/provider/package/darwinport.rb b/lib/puppet/provider/package/darwinport.rb
deleted file mode 100755
index c5f9ba28f..000000000
--- a/lib/puppet/provider/package/darwinport.rb
+++ /dev/null
@@ -1,86 +0,0 @@
-require 'puppet/provider/package'
-
-Puppet::Type.type(:package).provide :darwinport, :parent => Puppet::Provider::Package do
- desc "Package management using DarwinPorts on OS X."
-
- confine :operatingsystem => :darwin
- commands :port => "/opt/local/bin/port"
-
- def self.eachpkgashash
- # list out all of the packages
- open("| #{command(:port)} list installed") { |process|
- regex = %r{(\S+)\s+@(\S+)\s+(\S+)}
- fields = [:name, :ensure, :location]
- hash = {}
-
- # now turn each returned line into a package object
- process.each { |line|
- hash.clear
-
- if match = regex.match(line)
- fields.zip(match.captures) { |field,value|
- hash[field] = value
- }
-
- hash.delete :location
- hash[:provider] = self.name
- yield hash.dup
- else
- raise Puppet::DevError,
- "Failed to match dpkg line #{line}"
- end
- }
- }
- end
-
- def self.instances
- packages = []
-
- eachpkgashash do |hash|
- packages << new(hash)
- end
-
- packages
- end
-
- def install
- should = @resource.should(:ensure)
-
- # Seems like you can always say 'upgrade'
- output = port "upgrade", @resource[:name]
- if output =~ /^Error: No port/
- raise Puppet::ExecutionFailure, "Could not find package #{@resource[:name]}"
- end
- end
-
- def query
- version = nil
- self.class.eachpkgashash do |hash|
- return hash if hash[:name] == @resource[:name]
- end
-
- nil
- end
-
- def latest
- info = port :search, "^#{@resource[:name]}$"
-
- if $CHILD_STATUS != 0 or info =~ /^Error/
- return nil
- end
-
- ary = info.split(/\s+/)
- version = ary[2].sub(/^@/, '')
-
- version
- end
-
- def uninstall
- port :uninstall, @resource[:name]
- end
-
- def update
- install
- end
-end
-
diff --git a/lib/puppet/provider/package/gem.rb b/lib/puppet/provider/package/gem.rb
index 19414cec4..28731c849 100755
--- a/lib/puppet/provider/package/gem.rb
+++ b/lib/puppet/provider/package/gem.rb
@@ -22,7 +22,7 @@ Puppet::Type.type(:package).provide :gem, :parent => Puppet::Provider::Package d
end
if name = hash[:justme]
- command << name
+ command << name + "$"
end
begin
@@ -94,7 +94,7 @@ Puppet::Type.type(:package).provide :gem, :parent => Puppet::Provider::Package d
command << "--source" << "#{source}" << resource[:name]
end
else
- command << resource[:name]
+ command << "--no-rdoc" << "--no-ri" << resource[:name]
end
output = execute(command)
diff --git a/lib/puppet/provider/package/macports.rb b/lib/puppet/provider/package/macports.rb
new file mode 100755
index 000000000..c43eb72f3
--- /dev/null
+++ b/lib/puppet/provider/package/macports.rb
@@ -0,0 +1,106 @@
+require 'puppet/provider/package'
+
+Puppet::Type.type(:package).provide :macports, :parent => Puppet::Provider::Package do
+ desc "Package management using MacPorts on OS X.
+
+ Supports MacPorts versions and revisions, but not variants.
+ Variant preferences may be specified using the MacPorts variants.conf file
+ http://guide.macports.org/chunked/internals.configuration-files.html#internals.configuration-files.variants-conf
+
+ When specifying a version in the Puppet DSL, only specify the version, not the revision
+ Revisions are only used internally for ensuring the latest version/revision of a port.
+ "
+
+ confine :operatingsystem => :darwin
+ commands :port => "/opt/local/bin/port"
+
+ has_feature :installable
+ has_feature :uninstallable
+ has_feature :upgradeable
+ has_feature :versionable
+
+
+ def self.parse_installed_query_line(line)
+ regex = /(\S+)\s+@(\S+)_(\S+)\s+\(active\)/
+ fields = [:name, :ensure, :revision]
+ hash_from_line(line, regex, fields)
+ end
+
+ def self.parse_info_query_line(line)
+ regex = /(\S+)\s+(\S+)/
+ fields = [:version, :revision]
+ hash_from_line(line, regex, fields)
+ end
+
+ def self.hash_from_line(line, regex, fields)
+ hash = {}
+ if match = regex.match(line)
+ fields.zip(match.captures) { |field, value|
+ hash[field] = value
+ }
+ hash[:provider] = self.name
+ return hash
+ end
+ nil
+ end
+
+ def self.instances
+ packages = []
+ port("-q", :installed).each do |line|
+ if hash = parse_installed_query_line(line)
+ packages << new(hash)
+ end
+ end
+ packages
+ end
+
+ def install
+ should = @resource.should(:ensure)
+ if [:latest, :installed, :present].include?(should)
+ output = port("-q", :install, @resource[:name])
+ else
+ output = port("-q", :install, @resource[:name], "@#{should}")
+ end
+ # MacPorts now correctly exits non-zero with appropriate errors in
+ # situations where a port cannot be found or installed.
+ end
+
+ def query
+ return self.class.parse_installed_query_line(port("-q", :installed, @resource[:name]))
+ end
+
+ def latest
+ # We need both the version and the revision to be confident
+ # we've got the latest revision of a specific version
+ # Note we're still not doing anything with variants here.
+ info_line = port("-q", :info, "--line", "--version", "--revision", @resource[:name])
+ return nil if info_line == ""
+
+ if newest = self.class.parse_info_query_line(info_line)
+ current = query
+ # We're doing some fiddling behind the scenes here to cope with updated revisions.
+ # If we're already at the latest version/revision, then just return the version
+ # so the current and desired values match. Otherwise return version and revision
+ # to trigger an upgrade to the latest revision.
+ if newest[:version] == current[:ensure] and newest[:revision] == current[:revision]
+ return current[:ensure]
+ else
+ return "#{newest[:version]}_#{newest[:revision]}"
+ end
+ end
+ nil
+ end
+
+ def uninstall
+ port("-q", :uninstall, @resource[:name])
+ end
+
+ def update
+ if query[:name] == @resource[:name] # 'port upgrade' cannot install new ports
+ port("-q", :upgrade, @resource[:name])
+ else
+ install
+ end
+ end
+end
+
diff --git a/lib/puppet/provider/service/debian.rb b/lib/puppet/provider/service/debian.rb
index 3d09e2849..58b808a8e 100755
--- a/lib/puppet/provider/service/debian.rb
+++ b/lib/puppet/provider/service/debian.rb
@@ -22,8 +22,12 @@ Puppet::Type.type(:service).provide :debian, :parent => :init do
# Remove the symlinks
def disable
- update_rc "-f", @resource[:name], "remove"
- update_rc @resource[:name], "stop", "00", "1", "2", "3", "4", "5", "6", "."
+ if `dpkg --compare-versions $(dpkg-query -W --showformat '${Version}' sysv-rc) ge 2.88 ; echo $?`.to_i == 0
+ update_rc @resource[:name], "disable"
+ else
+ update_rc "-f", @resource[:name], "remove"
+ update_rc @resource[:name], "stop", "00", "1", "2", "3", "4", "5", "6", "."
+ end
end
def enabled?
diff --git a/lib/puppet/rails/host.rb b/lib/puppet/rails/host.rb
index b9dea2a3d..e5360217c 100644
--- a/lib/puppet/rails/host.rb
+++ b/lib/puppet/rails/host.rb
@@ -1,3 +1,4 @@
+require 'puppet/node/environment'
require 'puppet/rails'
require 'puppet/rails/resource'
require 'puppet/rails/fact_name'
@@ -28,6 +29,12 @@ class Puppet::Rails::Host < ActiveRecord::Base
host
end
+ # Override the setter for environment to force it to be a string, lest it
+ # be YAML encoded. See #4487.
+ def environment=(value)
+ super value.to_s
+ end
+
# returns a hash of fact_names.name => [ fact_values ] for this host.
# Note that 'fact_values' is actually a list of the value instances, not
# just actual values.
diff --git a/lib/puppet/rails/inventory_node.rb b/lib/puppet/rails/inventory_node.rb
index 52f8621a4..da7e61040 100644
--- a/lib/puppet/rails/inventory_node.rb
+++ b/lib/puppet/rails/inventory_node.rb
@@ -3,6 +3,11 @@ require 'puppet/rails/inventory_fact'
class Puppet::Rails::InventoryNode < ::ActiveRecord::Base
has_many :facts, :class_name => "Puppet::Rails::InventoryFact", :foreign_key => :node_id, :dependent => :delete_all
+ if Puppet::Util.activerecord_version >= 3.0
+ # Prevents "DEPRECATION WARNING: Base.named_scope has been deprecated, please use Base.scope instead"
+ ActiveRecord::NamedScope::ClassMethods.module_eval { alias :named_scope :scope }
+ end
+
named_scope :has_fact_with_value, lambda { |name,value|
{
:conditions => ["inventory_facts.name = ? AND inventory_facts.value = ?", name, value],
diff --git a/lib/puppet/resource/type_collection.rb b/lib/puppet/resource/type_collection.rb
index 277d37b18..4210475ad 100644
--- a/lib/puppet/resource/type_collection.rb
+++ b/lib/puppet/resource/type_collection.rb
@@ -162,17 +162,22 @@ class Puppet::Resource::TypeCollection
parser.string = code
else
file = Puppet.settings.value(:manifest, environment.to_s)
- return unless File.exist?(file)
parser.file = file
end
parser.parse
rescue => detail
+ @parse_failed = true
+
msg = "Could not parse for environment #{environment}: #{detail}"
error = Puppet::Error.new(msg)
error.set_backtrace(detail.backtrace)
raise error
end
+ def require_reparse?
+ @parse_failed || stale?
+ end
+
def stale?
@watched_files.values.detect { |file| file.changed? }
end
diff --git a/lib/puppet/simple_graph.rb b/lib/puppet/simple_graph.rb
index c5dac0f6c..c658b3b92 100644
--- a/lib/puppet/simple_graph.rb
+++ b/lib/puppet/simple_graph.rb
@@ -329,7 +329,7 @@ class Puppet::SimpleGraph
children = other.adjacent(container, :direction => :out)
# MQR TODO: Luke suggests that it should be possible to refactor the system so that
- # container nodes are retained, thus obviating the need for the whit.
+ # container nodes are retained, thus obviating the need for the whit.
children = [whit_class.new(:name => container.name, :catalog => other)] if children.empty?
# First create new edges for each of the :in edges
diff --git a/lib/puppet/transaction.rb b/lib/puppet/transaction.rb
index aa650eea1..48154ad6f 100644
--- a/lib/puppet/transaction.rb
+++ b/lib/puppet/transaction.rb
@@ -47,7 +47,7 @@ class Puppet::Transaction
def apply(resource, ancestor = nil)
status = resource_harness.evaluate(resource)
add_resource_status(status)
- event_manager.queue_events(ancestor || resource, status.events)
+ event_manager.queue_events(ancestor || resource, status.events) unless status.failed?
rescue => detail
resource.err "Could not evaluate: #{detail}"
end
diff --git a/lib/puppet/transaction/report.rb b/lib/puppet/transaction/report.rb
index 8e04759ad..16fee42ae 100644
--- a/lib/puppet/transaction/report.rb
+++ b/lib/puppet/transaction/report.rb
@@ -80,30 +80,49 @@ class Puppet::Transaction::Report
host
end
- # Provide a summary of this report.
+ # Provide a human readable textual summary of this report.
def summary
+ report = raw_summary
+
ret = ""
+ report.keys.sort { |a,b| a.to_s <=> b.to_s }.each do |key|
+ ret += "#{Puppet::Util::Metric.labelize(key)}:\n"
- @metrics.sort { |a,b| a[1].label <=> b[1].label }.each do |name, metric|
- ret += "#{metric.label}:\n"
- metric.values.sort { |a,b|
+ report[key].keys.sort { |a,b|
# sort by label
- if a[0] == :total
+ if a == :total
1
- elsif b[0] == :total
+ elsif b == :total
-1
else
- a[1] <=> b[1]
+ report[key][a].to_s <=> report[key][b].to_s
end
- }.each do |name, label, value|
+ }.each do |label|
+ value = report[key][label]
next if value == 0
value = "%0.2f" % value if value.is_a?(Float)
- ret += " %15s %s\n" % [label + ":", value]
+ ret += " %15s %s\n" % [Puppet::Util::Metric.labelize(label) + ":", value]
end
end
ret
end
+ # Provide a raw hash summary of this report.
+ def raw_summary
+ report = {}
+
+ @metrics.each do |name, metric|
+ key = metric.name.to_s
+ report[key] = {}
+ metric.values.each do |name, label, value|
+ report[key][name.to_s] = value
+ end
+ report[key]["total"] = 0 unless key == "time" or report[key].include?("total")
+ end
+ (report["time"] ||= {})["last_run"] = Time.now.tv_sec
+ report
+ end
+
# Based on the contents of this report's metrics, compute a single number
# that represents the report. The resulting number is a bitmask where
# individual bits represent the presence of different metrics.
@@ -142,7 +161,6 @@ class Puppet::Transaction::Report
metrics["total"] = resource_statuses.length
resource_statuses.each do |name, status|
-
Puppet::Resource::Status::STATES.each do |state|
metrics[state.to_s] += 1 if status.send(state)
end
diff --git a/lib/puppet/type/cron.rb b/lib/puppet/type/cron.rb
index 4b18e71f9..4f6ea733c 100755
--- a/lib/puppet/type/cron.rb
+++ b/lib/puppet/type/cron.rb
@@ -226,7 +226,9 @@ Puppet::Type.newtype(:cron) do
end
newproperty(:special) do
- desc "Special schedules"
+ desc "A special value such as 'reboot' or 'annually'.
+ Only available on supported systems such as Vixie Cron.
+ Overrides more specific time of day/week settings."
def specials
%w{reboot yearly annually monthly weekly daily midnight hourly}
diff --git a/lib/puppet/type/exec.rb b/lib/puppet/type/exec.rb
index 5ed2b104c..be0ece023 100755
--- a/lib/puppet/type/exec.rb
+++ b/lib/puppet/type/exec.rb
@@ -23,17 +23,15 @@ module Puppet
you are doing a lot of work with `exec`, please at least notify
us at Puppet Labs what you are doing, and hopefully we can work with
you to get a native resource type for the work you are doing.
-
- **Autorequires:** If Puppet is managing an exec's cwd or the executable file used in an exec's command, the exec resource will autorequire those files. If Puppet is managing the user that an exec should run as, the exec resource will autorequire that user."
- require 'open3'
+ **Autorequires:** If Puppet is managing an exec's cwd or the executable file used in an exec's command, the exec resource will autorequire those files. If Puppet is managing the user that an exec should run as, the exec resource will autorequire that user."
# Create a new check mechanism. It's basically just a parameter that
# provides one extra 'check' method.
- def self.newcheck(name, &block)
+ def self.newcheck(name, options = {}, &block)
@checks ||= {}
- check = newparam(name, &block)
+ check = newparam(name, options, &block)
@checks[name] = check
end
@@ -65,9 +63,11 @@ module Puppet
# First verify that all of our checks pass.
def retrieve
- # Default to somethinng
-
- if @resource.check
+ # We need to return :notrun to trigger evaluation; when that isn't
+ # true, we *LIE* about what happened and return a "success" for the
+ # value, which causes us to be treated as in_sync?, which means we
+ # don't actually execute anything. I think. --daniel 2011-03-10
+ if @resource.check_all_attributes
return :notrun
else
return self.should
@@ -89,7 +89,7 @@ module Puppet
tries.times do |try|
# Only add debug messages for tries > 1 to reduce log spam.
debug("Exec try #{try+1}/#{tries}") if tries > 1
- @output, @status = @resource.run(self.resource[:command])
+ @output, @status = provider.run(self.resource[:command])
break if self.should.include?(@status.exitstatus.to_s)
if try_sleep > 0 and tries > 1
debug("Sleeping for #{try_sleep} seconds between tries")
@@ -139,7 +139,7 @@ module Puppet
newparam(:path) do
desc "The search path used for command execution.
Commands must be fully qualified if no path is specified. Paths
- can be specified as an array or as a colon-separated list."
+ can be specified as an array or as a colon separated list."
# Support both arrays and colon-separated fields.
def value=(*values)
@@ -176,21 +176,9 @@ module Puppet
# Validation is handled by the SUIDManager class.
end
- newparam(:cwd) do
+ newparam(:cwd, :parent => Puppet::Parameter::Path) do
desc "The directory from which to run the command. If
this directory does not exist, the command will fail."
-
- validate do |dir|
- unless dir =~ /^#{File::SEPARATOR}/
- self.fail("CWD must be a fully qualified path")
- end
- end
-
- munge do |dir|
- dir = dir[0] if dir.is_a?(Array)
-
- dir
- end
end
newparam(:logoutput) do
@@ -209,7 +197,7 @@ module Puppet
for refreshing."
validate do |command|
- @resource.validatecmd(command)
+ provider.validatecmd(command)
end
end
@@ -241,19 +229,17 @@ module Puppet
newparam(:timeout) do
desc "The maximum time the command should take. If the command takes
longer than the timeout, the command is considered to have failed
- and will be stopped. Use any negative number to disable the timeout.
+ and will be stopped. Use 0 to disable the timeout.
The time is specified in seconds."
munge do |value|
value = value.shift if value.is_a?(Array)
- if value.is_a?(String)
- unless value =~ /^[-\d.]+$/
- raise ArgumentError, "The timeout must be a number."
- end
- Float(value)
- else
- value
+ begin
+ value = Float(value)
+ rescue ArgumentError => e
+ raise ArgumentError, "The timeout must be a number."
end
+ [value, 0.0].max
end
defaultto 300
@@ -333,7 +319,7 @@ module Puppet
end
end
- newcheck(:creates) do
+ newcheck(:creates, :parent => Puppet::Parameter::Path) do
desc "A file that this command creates. If this
parameter is provided, then the command will only be run
if the specified file does not exist:
@@ -346,19 +332,7 @@ module Puppet
"
- # FIXME if they try to set this and fail, then we should probably
- # fail the entire exec, right?
- validate do |files|
- files = [files] unless files.is_a? Array
-
- files.each do |file|
- self.fail("'creates' must be set to a fully qualified path") unless file
-
- unless file =~ %r{^#{File::SEPARATOR}}
- self.fail "'creates' files must be fully qualified."
- end
- end
- end
+ accept_arrays
# If the file exists, return false (i.e., don't run the command),
# else return true
@@ -386,15 +360,15 @@ module Puppet
validate do |cmds|
cmds = [cmds] unless cmds.is_a? Array
- cmds.each do |cmd|
- @resource.validatecmd(cmd)
+ cmds.each do |command|
+ provider.validatecmd(command)
end
end
# Return true if the command does not return 0.
def check(value)
begin
- output, status = @resource.run(value, true)
+ output, status = provider.run(value, true)
rescue Timeout::Error
err "Check #{value.inspect} exceeded timeout"
return false
@@ -428,15 +402,15 @@ module Puppet
validate do |cmds|
cmds = [cmds] unless cmds.is_a? Array
- cmds.each do |cmd|
- @resource.validatecmd(cmd)
+ cmds.each do |command|
+ provider.validatecmd(command)
end
end
# Return true if the command returns 0.
def check(value)
begin
- output, status = @resource.run(value, true)
+ output, status = provider.run(value, true)
rescue Timeout::Error
err "Check #{value.inspect} exceeded timeout"
return false
@@ -450,7 +424,7 @@ module Puppet
@isomorphic = false
validate do
- validatecmd(self[:command])
+ provider.validatecmd(self[:command])
end
# FIXME exec should autorequire any exec that 'creates' our cwd
@@ -503,7 +477,7 @@ module Puppet
# Verify that we pass all of the checks. The argument determines whether
# we skip the :refreshonly check, which is necessary because we now check
# within refresh
- def check(refreshing = false)
+ def check_all_attributes(refreshing = false)
self.class.checks.each { |check|
next if refreshing and check == :refreshonly
if @parameters.include?(check)
@@ -518,32 +492,6 @@ module Puppet
true
end
- # Verify that we have the executable
- def checkexe(cmd)
- exe = extractexe(cmd)
-
- if self[:path]
- if Puppet.features.posix? and !File.exists?(exe)
- withenv :PATH => self[:path].join(File::PATH_SEPARATOR) do
- exe = which(exe) || raise(ArgumentError,"Could not find command '#{exe}'")
- end
- elsif Puppet.features.microsoft_windows? and !File.exists?(exe)
- self[:path].each do |path|
- [".exe", ".ps1", ".bat", ".com", ""].each do |extension|
- file = File.join(path, exe+extension)
- return if File.exists?(file)
- end
- end
- end
- end
-
- raise ArgumentError, "Could not find executable '#{exe}'" unless FileTest.exists?(exe)
- unless FileTest.executable?(exe)
- raise ArgumentError,
- "'#{exe}' is not executable"
- end
- end
-
def output
if self.property(:returns).nil?
return nil
@@ -554,98 +502,13 @@ module Puppet
# Run the command, or optionally run a separately-specified command.
def refresh
- if self.check(true)
+ if self.check_all_attributes(true)
if cmd = self[:refresh]
- self.run(cmd)
+ provider.run(cmd)
else
self.property(:returns).sync
end
end
end
-
- # Run a command.
- def run(command, check = false)
- output = nil
- status = nil
-
- dir = nil
-
- checkexe(command)
-
- if dir = self[:cwd]
- unless File.directory?(dir)
- if check
- dir = nil
- else
- self.fail "Working directory '#{dir}' does not exist"
- end
- end
- end
-
- dir ||= Dir.pwd
-
- if check
- debug "Executing check '#{command}'"
- else
- debug "Executing '#{command}'"
- end
- begin
- # Do our chdir
- Dir.chdir(dir) do
- environment = {}
-
- environment[:PATH] = self[:path].join(":") if self[:path]
-
- if envlist = self[:environment]
- envlist = [envlist] unless envlist.is_a? Array
- envlist.each do |setting|
- if setting =~ /^(\w+)=((.|\n)+)$/
- name = $1
- value = $2
- if environment.include? name
- warning(
- "Overriding environment setting '#{name}' with '#{value}'"
- )
- end
- environment[name] = value
- else
- warning "Cannot understand environment setting #{setting.inspect}"
- end
- end
- end
-
- withenv environment do
- Timeout::timeout(self[:timeout]) do
- output, status = Puppet::Util::SUIDManager.run_and_capture(
- [command], self[:user], self[:group]
- )
- end
- # The shell returns 127 if the command is missing.
- if status.exitstatus == 127
- raise ArgumentError, output
- end
- end
- end
- rescue Errno::ENOENT => detail
- self.fail detail.to_s
- end
-
- return output, status
- end
-
- def validatecmd(cmd)
- exe = extractexe(cmd)
- # if we're not fully qualified, require a path
- self.fail "'#{cmd}' is not qualified and no path was specified. Please qualify the command or specify a path." if File.expand_path(exe) != exe and self[:path].nil?
- end
-
- def extractexe(cmd)
- # easy case: command was quoted
- if cmd =~ /^"([^"]+)"/
- $1
- else
- cmd.split(/ /)[0]
- end
- end
end
end
diff --git a/lib/puppet/type/file.rb b/lib/puppet/type/file.rb
index e1a4ecbb9..630ebe5de 100644
--- a/lib/puppet/type/file.rb
+++ b/lib/puppet/type/file.rb
@@ -122,7 +122,18 @@ Puppet::Type.newtype(:file) do
newparam(:recurse) do
desc "Whether and how deeply to do recursive
- management."
+ management. Options are:
+
+ * `inf,true` --- Regular style recursion on both remote and local
+ directory structure.
+ * `remote` --- Descends recursively into the remote directory
+ but not the local directory. Allows copying of
+ a few files into a directory containing many
+ unmanaged files without scanning all the local files.
+ * `false` --- Default of no recursion.
+ * `[0-9]+` --- Same as true, but limit recursion. Warning: this syntax
+ has been deprecated in favor of the `recurselimit` attribute.
+ "
newvalues(:true, :false, :inf, :remote, /^[0-9]+$/)
diff --git a/lib/puppet/type/file/source.rb b/lib/puppet/type/file/source.rb
index 6dda7957c..6ebec51fe 100755
--- a/lib/puppet/type/file/source.rb
+++ b/lib/puppet/type/file/source.rb
@@ -114,6 +114,7 @@ module Puppet
param_name = (metadata_method == :checksum) ? :content : metadata_method
next if metadata_method == :owner and !Puppet.features.root?
next if metadata_method == :checksum and metadata.ftype == "directory"
+ next if metadata_method == :checksum and metadata.ftype == "link" and metadata.links == :manage
if resource[param_name].nil? or resource[param_name] == :absent
resource[param_name] = metadata.send(metadata_method)
diff --git a/lib/puppet/type/group.rb b/lib/puppet/type/group.rb
index cde1cfd65..066bd49df 100755
--- a/lib/puppet/type/group.rb
+++ b/lib/puppet/type/group.rb
@@ -1,4 +1,3 @@
-
require 'etc'
require 'facter'
@@ -15,6 +14,9 @@ module Puppet
feature :manages_members,
"For directories where membership is an attribute of groups not users."
+ feature :system_groups,
+ "The provider allows you to create system groups with lower GIDs."
+
ensurable do
desc "Create or remove the group."
@@ -95,5 +97,13 @@ module Puppet
defaultto false
end
+
+ newparam(:system, :boolean => true) do
+ desc "Whether the group is a system group with lower GID."
+
+ newvalues(:true, :false)
+
+ defaultto false
+ end
end
end
diff --git a/lib/puppet/type/service.rb b/lib/puppet/type/service.rb
index 0d09c3d5d..3658e2837 100644
--- a/lib/puppet/type/service.rb
+++ b/lib/puppet/type/service.rb
@@ -8,19 +8,24 @@ module Puppet
newtype(:service) do
@doc = "Manage running services. Service support unfortunately varies
- widely by platform -- some platforms have very little if any
+ widely by platform --- some platforms have very little if any
concept of a running service, and some have a very codified and
powerful concept. Puppet's service support will generally be able
- to make up for any inherent shortcomings (e.g., if there is no
+ to do the right thing regardless (e.g., if there is no
'status' command, then Puppet will look in the process table for a
command matching the service name), but the more information you
- can provide the better behaviour you will get. Or, you can just
- use a platform that has very good service support.
+ can provide, the better behaviour you will get. In particular, any
+ virtual services that don't have a predictable entry in the process table
+ (for example, `network` on Red Hat/CentOS systems) will manifest odd
+ behavior on restarts if you don't specify `hasstatus` or a `status`
+ command.
Note that if a `service` receives an event from another resource,
the service will get restarted. The actual command to restart the
- service depends on the platform. You can provide a special command
- for restarting with the `restart` attribute."
+ service depends on the platform. You can provide an explicit command
+ for restarting with the `restart` attribute, or use the init script's
+ restart command with the `hasrestart` attribute; if you do neither,
+ the service's stop and start commands will be used."
feature :refreshable, "The provider can restart the service.",
:methods => [:restart]
@@ -93,11 +98,14 @@ module Puppet
that a large number of init scripts on different platforms do
not support any kind of status command; thus, you must specify
manually whether the service you are running has such a
- command (or you can specify a specific command using the
- `status` parameter).
-
- If you do not specify anything, then the service name will be
- looked for in the process table."
+ command. Alternately, you can provide a specific command using the
+ `status` attribute.
+
+ If you specify neither of these, then Puppet will look for the
+ service name in the process table. Be aware that 'virtual' init
+ scripts such as networking will respond poorly to refresh events
+ (via notify and subscribe relationships) if you don't override
+ this default behavior."
newvalues(:true, :false)
end
diff --git a/lib/puppet/util/command_line.rb b/lib/puppet/util/command_line.rb
index 7f74d266a..52b5f81ef 100644
--- a/lib/puppet/util/command_line.rb
+++ b/lib/puppet/util/command_line.rb
@@ -1,9 +1,10 @@
+require "puppet/util/plugins"
+
module Puppet
module Util
class CommandLine
- LegacyName = Hash.new{|h,k| k}.update(
- {
+ LegacyName = Hash.new{|h,k| k}.update(
'agent' => 'puppetd',
'cert' => 'puppetca',
'doc' => 'puppetdoc',
@@ -13,9 +14,8 @@ module Puppet
'queue' => 'puppetqd',
'resource' => 'ralsh',
'kick' => 'puppetrun',
- 'master' => 'puppetmasterd',
-
- })
+ 'master' => 'puppetmasterd'
+ )
def initialize( zero = $0, argv = ARGV, stdin = STDIN )
@zero = zero
@@ -23,6 +23,7 @@ module Puppet
@stdin = stdin
@subcommand_name, @args = subcommand_and_args( @zero, @argv, @stdin )
+ Puppet::Plugins.on_commandline_initialization(:command_line_object => self)
end
attr :subcommand_name
@@ -56,21 +57,23 @@ module Puppet
puts usage_message
elsif available_subcommands.include?(subcommand_name) #subcommand
require_application subcommand_name
- Puppet::Application.find(subcommand_name).new(self).run
+ app = Puppet::Application.find(subcommand_name).new(self)
+ Puppet::Plugins.on_application_initialization(:appliation_object => self)
+ app.run
else
abort "Error: Unknown command #{subcommand_name}.\n#{usage_message}" unless execute_external_subcommand
end
end
def execute_external_subcommand
- external_command = "puppet-#{subcommand_name}"
+ external_command = "puppet-#{subcommand_name}"
- require 'puppet/util'
- path_to_subcommand = Puppet::Util.which( external_command )
- return false unless path_to_subcommand
+ require 'puppet/util'
+ path_to_subcommand = Puppet::Util.which( external_command )
+ return false unless path_to_subcommand
- system( path_to_subcommand, *args )
- true
+ system( path_to_subcommand, *args )
+ true
end
def legacy_executable_name
diff --git a/lib/puppet/util/command_line/puppetrun b/lib/puppet/util/command_line/puppetrun
index 7eba3b2c4..3437405b0 100755
--- a/lib/puppet/util/command_line/puppetrun
+++ b/lib/puppet/util/command_line/puppetrun
@@ -107,7 +107,6 @@
# option requires LDAP support at this point.
#
# ping::
-#
# Do a ICMP echo against the target host. Skip hosts that don't respond to ping.
#
# = Example
diff --git a/lib/puppet/util/loadedfile.rb b/lib/puppet/util/loadedfile.rb
index 735dba459..d2f5d0923 100755
--- a/lib/puppet/util/loadedfile.rb
+++ b/lib/puppet/util/loadedfile.rb
@@ -34,10 +34,6 @@ module Puppet
# Create the file. Must be passed the file path.
def initialize(file)
@file = file
- unless FileTest.exists?(@file)
- raise Puppet::NoSuchFile,
- "Can not use a non-existent file for parsing"
- end
@statted = 0
@stamp = nil
@tstamp = stamp
@@ -50,7 +46,7 @@ module Puppet
@statted = Time.now.to_i
begin
@stamp = File.stat(@file).ctime
- rescue Errno::ENOENT
+ rescue Errno::ENOENT, Errno::ENOTDIR
@stamp = Time.now
end
end
diff --git a/lib/puppet/util/metric.rb b/lib/puppet/util/metric.rb
index 09bbb6137..835e1d610 100644
--- a/lib/puppet/util/metric.rb
+++ b/lib/puppet/util/metric.rb
@@ -122,7 +122,7 @@ class Puppet::Util::Metric
def initialize(name,label = nil)
@name = name.to_s
- @label = label || labelize(name)
+ @label = label || self.class.labelize(name)
@values = []
end
@@ -133,7 +133,7 @@ class Puppet::Util::Metric
def newvalue(name,value,label = nil)
raise ArgumentError.new("metric name #{name.inspect} is not a string") unless name.is_a? String
- label ||= labelize(name)
+ label ||= self.class.labelize(name)
@values.push [name,label,value]
end
@@ -174,10 +174,8 @@ class Puppet::Util::Metric
@values.sort { |a, b| a[1] <=> b[1] }
end
- private
-
# Convert a name into a label.
- def labelize(name)
+ def self.labelize(name)
name.to_s.capitalize.gsub("_", " ")
end
end
diff --git a/lib/puppet/util/plugins.rb b/lib/puppet/util/plugins.rb
new file mode 100644
index 000000000..105fdcd75
--- /dev/null
+++ b/lib/puppet/util/plugins.rb
@@ -0,0 +1,82 @@
+#
+# This system manages an extensible set of metadata about plugins which it
+# collects by searching for files named "plugin_init.rb" in a series of
+# directories. Initially, these are simply the $LOAD_PATH.
+#
+# The contents of each file found is executed in the context of a Puppet::Plugins
+# object (and thus scoped). An example file might contain:
+#
+# -------------------------------------------------------
+# @name = "Greet the CA"
+#
+# @description = %q{
+# This plugin causes a friendly greeting to print out on a master
+# that is operating as the CA, after it has been set up but before
+# it does anything.
+# }
+#
+# def after_application_setup(options)
+# if options[:application_object].is_a?(Puppet::Application::Master) && Puppet::SSL::CertificateAuthority.ca?
+# puts "Hey, this is the CA!"
+# end
+# end
+# -------------------------------------------------------
+#
+# Note that the instance variables are local to this Puppet::Plugin (and so may be used
+# for maintaining state, etc.) but the plugin system does not provide any thread safety
+# assurances, so they may not be adequate for some complex use cases.
+#
+#
+module Puppet
+ class Plugins
+ Paths = [] # Where we might find plugin initialization code
+ Loaded = [] # Code we have found (one-to-one with paths once searched)
+ #
+ # Return all the Puppet::Plugins we know about, searching any new paths
+ #
+ def self.known
+ Paths[Loaded.length...Paths.length].each { |path|
+ file = File.join(path,'plugin_init.rb')
+ Loaded << (File.exist?(file) && new(file))
+ }
+ Loaded.compact
+ end
+ #
+ # Add more places to look for plugins without adding duplicates or changing the
+ # order of ones we've already found.
+ #
+ def self.look_in(*paths)
+ Paths.replace Paths | paths.flatten.collect { |path| File.expand_path(path) }
+ end
+ #
+ # Initially just look in $LOAD_PATH
+ #
+ look_in $LOAD_PATH
+ #
+ # Calling methods (hooks) on the class calls the method of the same name on
+ # all plugins that use that hook, passing in the same arguments to each
+ # and returning an array containing the results returned by each plugin as
+ # an array of [plugin_name,result] pairs.
+ #
+ def self.method_missing(hook,*args,&block)
+ known.
+ select { |p| p.respond_to? hook }.
+ collect { |p| [p.name,p.send(hook,*args,&block)] }
+ end
+ #
+ #
+ #
+ attr_reader :path,:name
+ def initialize(path)
+ @name = @path = path
+ class << self
+ private
+ def define_hooks
+ eval File.read(path),nil,path,1
+ end
+ end
+ define_hooks
+ end
+ end
+end
+
diff --git a/lib/puppet/util/queue.rb b/lib/puppet/util/queue.rb
index 02357742a..636bdcf2e 100644
--- a/lib/puppet/util/queue.rb
+++ b/lib/puppet/util/queue.rb
@@ -30,7 +30,7 @@ require 'puppet/util/instance_loader'
#
# The client plugins are expected to implement an interface similar to that of Stomp::Client:
# * <tt>new</tt> should return a connected, ready-to-go client instance. Note that no arguments are passed in.
-# * <tt>send_message(queue, message)</tt> should send the _message_ to the specified _queue_.
+# * <tt>publish_message(queue, message)</tt> should publish the _message_ to the specified _queue_.
# * <tt>subscribe(queue)</tt> _block_ subscribes to _queue_ and executes _block_ upon receiving a message.
# * _queue_ names are simple names independent of the message broker or client library. No "/queue/" prefixes like in Stomp::Client.
module Puppet::Util::Queue
diff --git a/lib/puppet/util/queue/stomp.rb b/lib/puppet/util/queue/stomp.rb
index c18edae6a..cabc56627 100644
--- a/lib/puppet/util/queue/stomp.rb
+++ b/lib/puppet/util/queue/stomp.rb
@@ -28,8 +28,8 @@ class Puppet::Util::Queue::Stomp
end
end
- def send_message(target, msg)
- stomp_client.send(stompify_target(target), msg, :persistent => true)
+ def publish_message(target, msg)
+ stomp_client.publish(stompify_target(target), msg, :persistent => true)
end
def subscribe(target)