diff --git a/lib/puppet/application/doc.rb b/lib/puppet/application/doc.rb
index 6ebb47563..1f6c63286 100644
--- a/lib/puppet/application/doc.rb
+++ b/lib/puppet/application/doc.rb
@@ -1,207 +1,181 @@
require 'puppet/application'
class Puppet::Application::Doc < Puppet::Application
should_not_parse_config
run_mode :master
attr_accessor :unknown_args, :manifest
def preinit
{:references => [], :mode => :text, :format => :to_rest }.each do |name,value|
options[name] = value
end
@unknown_args = []
@manifest = false
end
option("--all","-a")
option("--outputdir OUTPUTDIR","-o")
option("--verbose","-v")
option("--debug","-d")
option("--charset CHARSET")
option("--format FORMAT", "-f") do |arg|
method = "to_#{arg}"
require 'puppet/util/reference'
if Puppet::Util::Reference.method_defined?(method)
options[:format] = method
else
raise "Invalid output format #{arg}"
end
end
option("--mode MODE", "-m") do |arg|
require 'puppet/util/reference'
if Puppet::Util::Reference.modes.include?(arg) or arg.intern==:rdoc
options[:mode] = arg.intern
else
raise "Invalid output mode #{arg}"
end
end
option("--list", "-l") do |arg|
require 'puppet/util/reference'
puts Puppet::Util::Reference.references.collect { |r| Puppet::Util::Reference.reference(r).doc }.join("\n")
exit(0)
end
option("--reference REFERENCE", "-r") do |arg|
options[:references] << arg.intern
end
def handle_unknown( opt, arg )
@unknown_args << {:opt => opt, :arg => arg }
true
end
def run_command
- return[:rdoc, :markdown].include?(options[:mode]) ? send(options[:mode]) : other
+ return[:rdoc].include?(options[:mode]) ? send(options[:mode]) : other
end
def rdoc
exit_code = 0
files = []
unless @manifest
env = Puppet::Node::Environment.new
files += env.modulepath
files << File.dirname(env[:manifest])
end
files += command_line.args
Puppet.info "scanning: #{files.inspect}"
Puppet.settings.setdefaults(
"puppetdoc",
"document_all" => [false, "Document all resources"]
)
Puppet.settings[:document_all] = options[:all] || false
begin
require 'puppet/util/rdoc'
if @manifest
Puppet::Util::RDoc.manifestdoc(files)
else
options[:outputdir] = "doc" unless options[:outputdir]
Puppet::Util::RDoc.rdoc(options[:outputdir], files, options[:charset])
end
rescue => detail
puts detail.backtrace if Puppet[:trace]
$stderr.puts "Could not generate documentation: #{detail}"
exit_code = 1
end
exit exit_code
end
- def markdown
- text = ""
- with_contents = false
- exit_code = 0
- require 'puppet/util/reference'
- options[:references].sort { |a,b| a.to_s <=> b.to_s }.each do |name|
- raise "Could not find reference #{name}" unless section = Puppet::Util::Reference.reference(name)
-
- begin
- # Add the per-section text, but with no ToC
- text += section.send(options[:format], with_contents)
- text += Puppet::Util::Reference.footer
- text.gsub!(/`\w+\s+([^`]+)`:trac:/) { |m| $1 }
- Puppet::Util::Reference.markdown(name, text)
- text = ""
- rescue => detail
- puts detail.backtrace
- $stderr.puts "Could not generate reference #{name}: #{detail}"
- exit_code = 1
- next
- end
- end
-
- exit exit_code
- end
-
def other
text = ""
with_contents = options[:references].length <= 1
exit_code = 0
require 'puppet/util/reference'
options[:references].sort { |a,b| a.to_s <=> b.to_s }.each do |name|
raise "Could not find reference #{name}" unless section = Puppet::Util::Reference.reference(name)
begin
# Add the per-section text, but with no ToC
text += section.send(options[:format], with_contents)
rescue => detail
puts detail.backtrace
$stderr.puts "Could not generate reference #{name}: #{detail}"
exit_code = 1
next
end
end
text += Puppet::Util::Reference.footer unless with_contents # We've only got one reference
# Replace the trac links, since they're invalid everywhere else
text.gsub!(/`\w+\s+([^`]+)`:trac:/) { |m| $1 }
if options[:mode] == :pdf
Puppet::Util::Reference.pdf(text)
else
puts text
end
exit exit_code
end
def setup
# sole manifest documentation
if command_line.args.size > 0
options[:mode] = :rdoc
@manifest = true
end
if options[:mode] == :rdoc
setup_rdoc
else
setup_reference
end
end
def setup_reference
if options[:all]
# Don't add dynamic references to the "all" list.
require 'puppet/util/reference'
options[:references] = Puppet::Util::Reference.references.reject do |ref|
Puppet::Util::Reference.reference(ref).dynamic?
end
end
options[:references] << :type if options[:references].empty?
end
def setup_rdoc(dummy_argument=:work_arround_for_ruby_GC_bug)
# consume the unknown options
# and feed them as settings
if @unknown_args.size > 0
@unknown_args.each do |option|
# force absolute path for modulepath when passed on commandline
if option[:opt]=="--modulepath" or option[:opt] == "--manifestdir"
option[:arg] = option[:arg].split(':').collect { |p| File.expand_path(p) }.join(':')
end
Puppet.settings.handlearg(option[:opt], option[:arg])
end
end
# Now parse the config
Puppet.parse_config
# Handle the logging settings.
if options[:debug] or options[:verbose]
if options[:debug]
Puppet::Util::Log.level = :debug
else
Puppet::Util::Log.level = :info
end
Puppet::Util::Log.newdestination(:console)
end
end
end
diff --git a/lib/puppet/indirector/facts/memory.rb b/lib/puppet/indirector/facts/memory.rb
index c4ca19da5..93682f456 100644
--- a/lib/puppet/indirector/facts/memory.rb
+++ b/lib/puppet/indirector/facts/memory.rb
@@ -1,9 +1,9 @@
require 'puppet/node/facts'
require 'puppet/indirector/memory'
class Puppet::Node::Facts::Memory < Puppet::Indirector::Memory
desc "Keep track of facts in memory but nowhere else. This is used for
- one-time compiles, such as what the stand-alone ``puppet`` does.
+ one-time compiles, such as what the stand-alone `puppet` does.
To use this terminus, you must load it with the data you want it
to contain."
end
diff --git a/lib/puppet/indirector/node/memory.rb b/lib/puppet/indirector/node/memory.rb
index ce4a52a8f..029926af8 100644
--- a/lib/puppet/indirector/node/memory.rb
+++ b/lib/puppet/indirector/node/memory.rb
@@ -1,10 +1,10 @@
require 'puppet/node'
require 'puppet/indirector/memory'
class Puppet::Node::Memory < Puppet::Indirector::Memory
desc "Keep track of nodes in memory but nowhere else. This is used for
- one-time compiles, such as what the stand-alone ``puppet`` does.
+ one-time compiles, such as what the stand-alone `puppet` does.
To use this terminus, you must load it with the data you want it
to contain; it is only useful for developers and should generally not
be chosen by a normal user."
end
diff --git a/lib/puppet/parameter/value_collection.rb b/lib/puppet/parameter/value_collection.rb
index a9fd20233..619e0731d 100644
--- a/lib/puppet/parameter/value_collection.rb
+++ b/lib/puppet/parameter/value_collection.rb
@@ -1,143 +1,143 @@
require 'puppet/parameter/value'
# A collection of values and regexes, used for specifying
# what values are allowed in a given parameter.
class Puppet::Parameter::ValueCollection
def aliasvalue(name, other)
other = other.to_sym
unless value = match?(other)
raise Puppet::DevError, "Cannot alias nonexistent value #{other}"
end
value.alias(name)
end
# Return a doc string for all of the values in this parameter/property.
def doc
unless defined?(@doc)
@doc = ""
unless values.empty?
@doc += " Valid values are "
@doc += @strings.collect do |value|
if aliases = value.aliases and ! aliases.empty?
- "``#{value.name}`` (also called ``#{aliases.join(", ")}``)"
+ "`#{value.name}` (also called `#{aliases.join(", ")}`)"
else
- "``#{value.name}``"
+ "`#{value.name}`"
end
end.join(", ") + "."
end
- @doc += " Values can match ``" + regexes.join("``, ``") + "``." unless regexes.empty?
+ @doc += " Values can match `" + regexes.join("`, `") + "`." unless regexes.empty?
end
@doc
end
# Does this collection contain any value definitions?
def empty?
@values.empty?
end
def initialize
# We often look values up by name, so a hash makes more sense.
@values = {}
# However, we want to retain the ability to match values in order,
# but we always prefer directly equality (i.e., strings) over regex matches.
@regexes = []
@strings = []
end
# Can we match a given value?
def match?(test_value)
# First look for normal values
if value = @strings.find { |v| v.match?(test_value) }
return value
end
# Then look for a regex match
@regexes.find { |v| v.match?(test_value) }
end
# If the specified value is allowed, then munge appropriately.
def munge(value)
return value if empty?
if instance = match?(value)
if instance.regex?
return value
else
return instance.name
end
else
return value
end
end
# Define a new valid value for a property. You must provide the value itself,
# usually as a symbol, or a regex to match the value.
#
# The first argument to the method is either the value itself or a regex.
# The second argument is an option hash; valid options are:
# * :event: The event that should be returned when this value is set.
# * :call: When to call any associated block. The default value
# is ``instead``, which means to call the value instead of calling the
# provider. You can also specify ``before`` or ``after``, which will
# call both the block and the provider, according to the order you specify
# (the ``first`` refers to when the block is called, not the provider).
def newvalue(name, options = {}, &block)
value = Puppet::Parameter::Value.new(name)
@values[value.name] = value
if value.regex?
@regexes << value
else
@strings << value
end
options.each { |opt, arg| value.send(opt.to_s + "=", arg) }
if block_given?
value.block = block
else
value.call = options[:call] || :none
end
value.method ||= "set_#{value.name}" if block_given? and ! value.regex?
value
end
# Define one or more new values for our parameter.
def newvalues(*names)
names.each { |name| newvalue(name) }
end
def regexes
@regexes.collect { |r| r.name.inspect }
end
# Verify that the passed value is valid.
def validate(value)
return if empty?
unless @values.detect { |name, v| v.match?(value) }
str = "Invalid value #{value.inspect}. "
str += "Valid values are #{values.join(", ")}. " unless values.empty?
str += "Valid values match #{regexes.join(", ")}." unless regexes.empty?
raise ArgumentError, str
end
end
# Return a single value instance.
def value(name)
@values[name]
end
# Return the list of valid values.
def values
@strings.collect { |s| s.name }
end
end
diff --git a/lib/puppet/property.rb b/lib/puppet/property.rb
index 282a52cf4..84e1a0360 100644
--- a/lib/puppet/property.rb
+++ b/lib/puppet/property.rb
@@ -1,324 +1,324 @@
# The virtual base class for properties, which are the self-contained building
# blocks for actually doing work on the system.
require 'puppet'
require 'puppet/parameter'
class Puppet::Property < Puppet::Parameter
require 'puppet/property/ensure'
# Because 'should' uses an array, we have a special method for handling
# it. We also want to keep copies of the original values, so that
# they can be retrieved and compared later when merging.
attr_reader :shouldorig
attr_writer :noop
class << self
attr_accessor :unmanaged
attr_reader :name
# Return array matching info, defaulting to just matching
# the first value.
def array_matching
@array_matching ||= :first
end
# Set whether properties should match all values or just the first one.
def array_matching=(value)
value = value.intern if value.is_a?(String)
raise ArgumentError, "Supported values for Property#array_matching are 'first' and 'all'" unless [:first, :all].include?(value)
@array_matching = value
end
end
# Look up a value's name, so we can find options and such.
def self.value_name(name)
if value = value_collection.match?(name)
value.name
end
end
# Retrieve an option set when a value was defined.
def self.value_option(name, option)
if value = value_collection.value(name)
value.send(option)
end
end
# Define a new valid value for a property. You must provide the value itself,
# usually as a symbol, or a regex to match the value.
#
# The first argument to the method is either the value itself or a regex.
# The second argument is an option hash; valid options are:
# * :method: The name of the method to define. Defaults to 'set_'.
# * :required_features: A list of features this value requires.
# * :event: The event that should be returned when this value is set.
# * :call: When to call any associated block. The default value
- # is ``instead``, which means to call the value instead of calling the
- # provider. You can also specify ``before`` or ``after``, which will
+ # is `instead`, which means to call the value instead of calling the
+ # provider. You can also specify `before` or `after`, which will
# call both the block and the provider, according to the order you specify
- # (the ``first`` refers to when the block is called, not the provider).
+ # (the `first` refers to when the block is called, not the provider).
def self.newvalue(name, options = {}, &block)
value = value_collection.newvalue(name, options, &block)
define_method(value.method, &value.block) if value.method and value.block
value
end
# Call the provider method.
def call_provider(value)
provider.send(self.class.name.to_s + "=", value)
rescue NoMethodError
self.fail "The #{provider.class.name} provider can not handle attribute #{self.class.name}"
end
# Call the dynamically-created method associated with our value, if
# there is one.
def call_valuemethod(name, value)
if method = self.class.value_option(name, :method) and self.respond_to?(method)
begin
event = self.send(method)
rescue Puppet::Error
raise
rescue => detail
puts detail.backtrace if Puppet[:trace]
error = Puppet::Error.new("Could not set '#{value} on #{self.class.name}: #{detail}", @resource.line, @resource.file)
error.set_backtrace detail.backtrace
raise error
end
elsif block = self.class.value_option(name, :block)
# FIXME It'd be better here to define a method, so that
# the blocks could return values.
self.instance_eval(&block)
else
devfail "Could not find method for value '#{name}'"
end
end
# How should a property change be printed as a string?
def change_to_s(current_value, newvalue)
begin
if current_value == :absent
return "defined '#{name}' as '#{should_to_s(newvalue)}'"
elsif newvalue == :absent or newvalue == [:absent]
return "undefined '#{name}' from '#{is_to_s(current_value)}'"
else
return "#{name} changed '#{is_to_s(current_value)}' to '#{should_to_s(newvalue)}'"
end
rescue Puppet::Error, Puppet::DevError
raise
rescue => detail
puts detail.backtrace if Puppet[:trace]
raise Puppet::DevError, "Could not convert change '#{name}' to string: #{detail}"
end
end
# Figure out which event to return.
def event_name
value = self.should
event_name = self.class.value_option(value, :event) and return event_name
name == :ensure or return (name.to_s + "_changed").to_sym
return (resource.type.to_s + case value
when :present; "_created"
when :absent; "_removed"
else
"_changed"
end).to_sym
end
# Return a modified form of the resource event.
def event
resource.event :name => event_name, :desired_value => should, :property => self, :source_description => path
end
attr_reader :shadow
# initialize our property
def initialize(hash = {})
super
if ! self.metaparam? and klass = Puppet::Type.metaparamclass(self.class.name)
setup_shadow(klass)
end
end
# Determine whether the property is in-sync or not. If @should is
# not defined or is set to a non-true value, then we do not have
# a valid value for it and thus consider the property to be in-sync
# since we cannot fix it. Otherwise, we expect our should value
# to be an array, and if @is matches any of those values, then
# we consider it to be in-sync.
def insync?(is)
return true unless @should
self.devfail "#{self.class.name}'s should is not array" unless @should.is_a?(Array)
# an empty array is analogous to no should values
return true if @should.empty?
# Look for a matching value
return (is == @should or is == @should.collect { |v| v.to_s }) if match_all?
@should.each { |val| return true if is == val or is == val.to_s }
# otherwise, return false
false
end
# because the @should and @is vars might be in weird formats,
# we need to set up a mechanism for pretty printing of the values
# default to just the values, but this way individual properties can
# override these methods
def is_to_s(currentvalue)
currentvalue
end
# Send a log message.
def log(msg)
Puppet::Util::Log.create(
-
+
:level => resource[:loglevel],
:message => msg,
-
+
:source => self
)
end
# Should we match all values, or just the first?
def match_all?
self.class.array_matching == :all
end
# Execute our shadow's munge code, too, if we have one.
def munge(value)
self.shadow.munge(value) if self.shadow
super
end
# each property class must define the name method, and property instances
# do not change that name
# this implicitly means that a given object can only have one property
# instance of a given property class
def name
self.class.name
end
# for testing whether we should actually do anything
def noop
# This is only here to make testing easier.
if @resource.respond_to?(:noop?)
@resource.noop?
else
if defined?(@noop)
@noop
else
Puppet[:noop]
end
end
end
# By default, call the method associated with the property name on our
# provider. In other words, if the property name is 'gid', we'll call
# 'provider.gid' to retrieve the current value.
def retrieve
provider.send(self.class.name)
end
# Set our value, using the provider, an associated block, or both.
def set(value)
# Set a name for looking up associated options like the event.
name = self.class.value_name(value)
call = self.class.value_option(name, :call) || :none
if call == :instead
call_valuemethod(name, value)
elsif call == :none
# They haven't provided a block, and our parent does not have
# a provider, so we have no idea how to handle this.
self.fail "#{self.class.name} cannot handle values of type #{value.inspect}" unless @resource.provider
call_provider(value)
else
# LAK:NOTE 20081031 This is a change in behaviour -- you could
# previously specify :call => [;before|:after], which would call
# the setter *in addition to* the block. I'm convinced this
# was never used, and it makes things unecessarily complicated.
# If you want to specify a block and still call the setter, then
# do so in the block.
devfail "Cannot use obsolete :call value '#{call}' for property '#{self.class.name}'"
end
end
# If there's a shadowing metaparam, instantiate it now.
# This allows us to create a property or parameter with the
# same name as a metaparameter, and the metaparam will only be
# stored as a shadow.
def setup_shadow(klass)
@shadow = klass.new(:resource => self.resource)
end
# Only return the first value
def should
return nil unless defined?(@should)
self.devfail "should for #{self.class.name} on #{resource.name} is not an array" unless @should.is_a?(Array)
if match_all?
return @should.collect { |val| self.unmunge(val) }
else
return self.unmunge(@should[0])
end
end
# Set the should value.
def should=(values)
values = [values] unless values.is_a?(Array)
@shouldorig = values
values.each { |val| validate(val) }
@should = values.collect { |val| self.munge(val) }
end
def should_to_s(newvalue)
[newvalue].flatten.join(" ")
end
def sync
devfail "Got a nil value for should" unless should
set(should)
end
# Verify that the passed value is valid.
# If the developer uses a 'validate' hook, this method will get overridden.
def unsafe_validate(value)
super
validate_features_per_value(value)
end
# Make sure that we've got all of the required features for a given value.
def validate_features_per_value(value)
if features = self.class.value_option(self.class.value_name(value), :required_features)
features = Array(features)
needed_features = features.collect { |f| f.to_s }.join(", ")
raise ArgumentError, "Provider must have features '#{needed_features}' to set '#{self.class.name}' to '#{value}'" unless provider.satisfies?(features)
end
end
# Just return any should value we might have.
def value
self.should
end
# Match the Parameter interface, but we really just use 'should' internally.
# Note that the should= method does all of the validation and such.
def value=(value)
self.should = value
end
end
diff --git a/lib/puppet/provider.rb b/lib/puppet/provider.rb
index 8f993dbc1..af792b623 100644
--- a/lib/puppet/provider.rb
+++ b/lib/puppet/provider.rb
@@ -1,281 +1,281 @@
# The container class for implementations.
class Puppet::Provider
include Puppet::Util
include Puppet::Util::Errors
include Puppet::Util::Warnings
extend Puppet::Util::Warnings
require 'puppet/provider/confiner'
extend Puppet::Provider::Confiner
Puppet::Util.logmethods(self, true)
class << self
# Include the util module so we have access to things like 'binary'
include Puppet::Util, Puppet::Util::Docs
include Puppet::Util::Logging
attr_accessor :name
# The source parameter exists so that providers using the same
# source can specify this, so reading doesn't attempt to read the
# same package multiple times.
attr_writer :source
# LAK 2007-05-09: Keep the model stuff around for backward compatibility
attr_reader :model
attr_accessor :resource_type
attr_writer :doc
end
# LAK 2007-05-09: Keep the model stuff around for backward compatibility
attr_reader :model
attr_accessor :resource
def self.command(name)
name = symbolize(name)
if defined?(@commands) and command = @commands[name]
# nothing
elsif superclass.respond_to? :command and command = superclass.command(name)
# nothing
else
raise Puppet::DevError, "No command #{name} defined for provider #{self.name}"
end
binary(command)
end
# Define commands that are not optional.
def self.commands(hash)
optional_commands(hash) do |name, path|
confine :exists => path, :for_binary => true
end
end
# Is the provided feature a declared feature?
def self.declared_feature?(name)
defined?(@declared_features) and @declared_features.include?(name)
end
# Does this implementation match all of the default requirements? If
# defaults are empty, we return false.
def self.default?
return false if @defaults.empty?
if @defaults.find do |fact, values|
values = [values] unless values.is_a? Array
if fval = Facter.value(fact).to_s and fval != ""
fval = fval.to_s.downcase.intern
else
return false
end
# If any of the values match, we're a default.
if values.find do |value| fval == value.to_s.downcase.intern end
false
else
true
end
end
return false
else
return true
end
end
# Store how to determine defaults.
def self.defaultfor(hash)
hash.each do |d,v|
@defaults[d] = v
end
end
def self.specificity
(@defaults.length * 100) + ancestors.select { |a| a.is_a? Class }.length
end
def self.initvars
@defaults = {}
@commands = {}
end
# The method for returning a list of provider instances. Note that it returns providers, preferably with values already
# filled in, not resources.
def self.instances
raise Puppet::DevError, "Provider #{self.name} has not defined the 'instances' class method"
end
# Create the methods for a given command.
def self.make_command_methods(name)
# Now define a method for that command
unless singleton_class.method_defined?(name)
meta_def(name) do |*args|
raise Puppet::Error, "Command #{name} is missing" unless command(name)
if args.empty?
cmd = [command(name)]
else
cmd = [command(name)] + args
end
# This might throw an ExecutionFailure, but the system above
# will catch it, if so.
return execute(cmd)
end
# And then define an instance method that just calls the class method.
# We need both, so both instances and classes can easily run the commands.
unless method_defined?(name)
define_method(name) do |*args|
self.class.send(name, *args)
end
end
end
end
# Create getter/setter methods for each property our resource type supports.
# They all get stored in @property_hash. This method is useful
# for those providers that use prefetch and flush.
def self.mkmodelmethods
warnonce "Provider.mkmodelmethods is deprecated; use Provider.mk_resource_methods"
mk_resource_methods
end
# Create getter/setter methods for each property our resource type supports.
# They all get stored in @property_hash. This method is useful
# for those providers that use prefetch and flush.
def self.mk_resource_methods
[resource_type.validproperties, resource_type.parameters].flatten.each do |attr|
attr = symbolize(attr)
next if attr == :name
define_method(attr) do
@property_hash[attr] || :absent
end
define_method(attr.to_s + "=") do |val|
@property_hash[attr] = val
end
end
end
self.initvars
# Define one or more binaries we'll be using. If a block is passed, yield the name
# and path to the block (really only used by 'commands').
def self.optional_commands(hash)
hash.each do |name, path|
name = symbolize(name)
@commands[name] = path
yield(name, path) if block_given?
# Now define the class and instance methods.
make_command_methods(name)
end
end
# Retrieve the data source. Defaults to the provider name.
def self.source
@source ||= self.name
end
# Does this provider support the specified parameter?
def self.supports_parameter?(param)
if param.is_a?(Class)
klass = param
else
unless klass = resource_type.attrclass(param)
raise Puppet::DevError, "'#{param}' is not a valid parameter for #{resource_type.name}"
end
end
return true unless features = klass.required_features
!!satisfies?(*features)
end
# def self.to_s
# unless defined?(@str)
# if self.resource_type
# @str = "#{resource_type.name} provider #{self.name}"
# else
# @str = "unattached provider #{self.name}"
# end
# end
# @str
# end
dochook(:defaults) do
if @defaults.length > 0
return " Default for " + @defaults.collect do |f, v|
- "``#{f}`` == ``#{v}``"
+ "`#{f}` == `#{v}`"
end.join(" and ") + "."
end
end
dochook(:commands) do
if @commands.length > 0
return " Required binaries: " + @commands.collect do |n, c|
- "``#{c}``"
+ "`#{c}`"
end.join(", ") + "."
end
end
dochook(:features) do
if features.length > 0
return " Supported features: " + features.collect do |f|
- "``#{f}``"
+ "`#{f}`"
end.join(", ") + "."
end
end
# Remove the reference to the resource, so GC can clean up.
def clear
@resource = nil
@model = nil
end
# Retrieve a named command.
def command(name)
self.class.command(name)
end
# Get a parameter value.
def get(param)
@property_hash[symbolize(param)] || :absent
end
def initialize(resource = nil)
if resource.is_a?(Hash)
# We don't use a duplicate here, because some providers (ParsedFile, at least)
# use the hash here for later events.
@property_hash = resource
elsif resource
@resource = resource
# LAK 2007-05-09: Keep the model stuff around for backward compatibility
@model = resource
@property_hash = {}
else
@property_hash = {}
end
end
def name
if n = @property_hash[:name]
return n
elsif self.resource
resource.name
else
raise Puppet::DevError, "No resource and no name in property hash in #{self.class.name} instance"
end
end
# Set passed params as the current values.
def set(params)
params.each do |param, value|
@property_hash[symbolize(param)] = value
end
end
def to_s
"#{@resource}(provider=#{self.class.name})"
end
end
diff --git a/lib/puppet/reference/providers.rb b/lib/puppet/reference/providers.rb
index b44b26a51..c85ad23ab 100644
--- a/lib/puppet/reference/providers.rb
+++ b/lib/puppet/reference/providers.rb
@@ -1,123 +1,123 @@
# This doesn't get stored in trac, since it changes every time.
providers = Puppet::Util::Reference.newreference :providers, :title => "Provider Suitability Report", :depth => 1, :dynamic => true, :doc => "Which providers are valid for this machine" do
types = []
Puppet::Type.loadall
Puppet::Type.eachtype do |klass|
next unless klass.providers.length > 0
types << klass
end
types.sort! { |a,b| a.name.to_s <=> b.name.to_s }
command_line = Puppet::Util::CommandLine.new
types.reject! { |type| ! command_line.args.include?(type.name.to_s) } unless command_line.args.empty?
ret = "Details about this host:\n\n"
# Throw some facts in there, so we know where the report is from.
["Ruby Version", "Puppet Version", "Operating System", "Operating System Release"].each do |label|
name = label.gsub(/\s+/, '')
value = Facter.value(name)
ret += option(label, value)
end
ret += "\n"
count = 1
# Produce output for each type.
types.each do |type|
features = type.features
ret += "\n" # add a trailing newline
# Now build up a table of provider suitability.
headers = %w{Provider Suitable?} + features.collect { |f| f.to_s }.sort
table_data = {}
functional = false
notes = []
begin
default = type.defaultprovider.name
rescue Puppet::DevError
default = "none"
end
type.providers.sort { |a,b| a.to_s <=> b.to_s }.each do |pname|
data = []
table_data[pname] = data
provider = type.provider(pname)
# Add the suitability note
if missing = provider.suitable?(false) and missing.empty?
data << "*X*"
suit = true
functional = true
else
data << "[#{count}]_" # A pointer to the appropriate footnote
suit = false
end
# Add a footnote with the details about why this provider is unsuitable, if that's the case
unless suit
details = ".. [#{count}]\n"
missing.each do |test, values|
case test
when :exists
details += " - Missing files #{values.join(", ")}\n"
when :variable
values.each do |name, facts|
if Puppet.settings.valid?(name)
details += " - Setting #{name} (currently #{Puppet.settings.value(name).inspect}) not in list #{facts.join(", ")}\n"
else
details += " - Fact #{name} (currently #{Facter.value(name).inspect}) not in list #{facts.join(", ")}\n"
end
end
when :true
details += " - Got #{values} true tests that should have been false\n"
when :false
details += " - Got #{values} false tests that should have been true\n"
when :feature
details += " - Missing features #{values.collect { |f| f.to_s }.join(",")}\n"
end
end
notes << details
count += 1
end
# Add a note for every feature
features.each do |feature|
if provider.features.include?(feature)
data << "*X*"
else
data << ""
end
end
end
ret += h(type.name.to_s + "_", 2)
- ret += ".. _#{type.name}: #{"http://docs.puppetlabs.com/references/stable/type.html##{type.name}"}\n\n"
+ ret += "[#{type.name}](#{"http://docs.puppetlabs.com/references/stable/type.html##{type.name}"})\n\n"
ret += option("Default provider", default)
ret += doctable(headers, table_data)
notes.each do |note|
ret += note + "\n"
end
ret += "\n"
end
ret += "\n"
ret
end
providers.header = "
-Puppet resource types are usually backed by multiple implementations called ``providers``,
+Puppet resource types are usually backed by multiple implementations called `providers`,
which handle variance between platforms and tools.
Different providers are suitable or unsuitable on different platforms based on things
like the presence of a given tool.
Here are all of the provider-backed types and their different providers. Any unmentioned
types do not use providers yet.
"
diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb
index c3855a400..53c23aa32 100644
--- a/lib/puppet/type.rb
+++ b/lib/puppet/type.rb
@@ -1,1889 +1,1889 @@
require 'puppet'
require 'puppet/util/log'
require 'puppet/util/metric'
require 'puppet/property'
require 'puppet/parameter'
require 'puppet/util'
require 'puppet/util/autoload'
require 'puppet/metatype/manager'
require 'puppet/util/errors'
require 'puppet/util/log_paths'
require 'puppet/util/logging'
require 'puppet/util/cacher'
require 'puppet/file_collection/lookup'
require 'puppet/util/tagging'
# see the bottom of the file for the rest of the inclusions
module Puppet
class Type
include Puppet::Util
include Puppet::Util::Errors
include Puppet::Util::LogPaths
include Puppet::Util::Logging
include Puppet::Util::Cacher
include Puppet::FileCollection::Lookup
include Puppet::Util::Tagging
###############################
# Code related to resource type attributes.
class << self
include Puppet::Util::ClassGen
include Puppet::Util::Warnings
attr_reader :properties
end
def self.states
warnonce "The states method is deprecated; use properties"
properties
end
# All parameters, in the appropriate order. The key_attributes come first, then
# the provider, then the properties, and finally the params and metaparams
# in the order they were specified in the files.
def self.allattrs
key_attributes | (parameters & [:provider]) | properties.collect { |property| property.name } | parameters | metaparams
end
# Retrieve an attribute alias, if there is one.
def self.attr_alias(param)
@attr_aliases[symbolize(param)]
end
# Create an alias to an existing attribute. This will cause the aliased
# attribute to be valid when setting and retrieving values on the instance.
def self.set_attr_alias(hash)
hash.each do |new, old|
@attr_aliases[symbolize(new)] = symbolize(old)
end
end
# Find the class associated with any given attribute.
def self.attrclass(name)
@attrclasses ||= {}
# We cache the value, since this method gets called such a huge number
# of times (as in, hundreds of thousands in a given run).
unless @attrclasses.include?(name)
@attrclasses[name] = case self.attrtype(name)
when :property; @validproperties[name]
when :meta; @@metaparamhash[name]
when :param; @paramhash[name]
end
end
@attrclasses[name]
end
# What type of parameter are we dealing with? Cache the results, because
# this method gets called so many times.
def self.attrtype(attr)
@attrtypes ||= {}
unless @attrtypes.include?(attr)
@attrtypes[attr] = case
when @validproperties.include?(attr); :property
when @paramhash.include?(attr); :param
when @@metaparamhash.include?(attr); :meta
end
end
@attrtypes[attr]
end
def self.eachmetaparam
@@metaparams.each { |p| yield p.name }
end
# Create the 'ensure' class. This is a separate method so other types
# can easily call it and create their own 'ensure' values.
def self.ensurable(&block)
if block_given?
self.newproperty(:ensure, :parent => Puppet::Property::Ensure, &block)
else
self.newproperty(:ensure, :parent => Puppet::Property::Ensure) do
self.defaultvalues
end
end
end
# Should we add the 'ensure' property to this class?
def self.ensurable?
# If the class has all three of these methods defined, then it's
# ensurable.
ens = [:exists?, :create, :destroy].inject { |set, method|
set &&= self.public_method_defined?(method)
}
ens
end
# Deal with any options passed into parameters.
def self.handle_param_options(name, options)
# If it's a boolean parameter, create a method to test the value easily
if options[:boolean]
define_method(name.to_s + "?") do
val = self[name]
if val == :true or val == true
return true
end
end
end
end
# Is the parameter in question a meta-parameter?
def self.metaparam?(param)
@@metaparamhash.include?(symbolize(param))
end
# Find the metaparameter class associated with a given metaparameter name.
def self.metaparamclass(name)
@@metaparamhash[symbolize(name)]
end
def self.metaparams
@@metaparams.collect { |param| param.name }
end
def self.metaparamdoc(metaparam)
@@metaparamhash[metaparam].doc
end
# Create a new metaparam. Requires a block and a name, stores it in the
# @parameters array, and does some basic checking on it.
def self.newmetaparam(name, options = {}, &block)
@@metaparams ||= []
@@metaparamhash ||= {}
name = symbolize(name)
param = genclass(
name,
:parent => options[:parent] || Puppet::Parameter,
:prefix => "MetaParam",
:hash => @@metaparamhash,
:array => @@metaparams,
:attributes => options[:attributes],
&block
)
# Grr.
param.required_features = options[:required_features] if options[:required_features]
handle_param_options(name, options)
param.metaparam = true
param
end
def self.key_attribute_parameters
@key_attribute_parameters ||= (
params = @parameters.find_all { |param|
param.isnamevar? or param.name == :name
}
)
end
def self.key_attributes
key_attribute_parameters.collect { |p| p.name }
end
def self.title_patterns
case key_attributes.length
when 0; []
when 1;
identity = lambda {|x| x}
[ [ /(.*)/m, [ [key_attributes.first, identity ] ] ] ]
else
raise Puppet::DevError,"you must specify title patterns when there are two or more key attributes"
end
end
def uniqueness_key
to_resource.uniqueness_key
end
# Create a new parameter. Requires a block and a name, stores it in the
# @parameters array, and does some basic checking on it.
def self.newparam(name, options = {}, &block)
options[:attributes] ||= {}
param = genclass(
name,
:parent => options[:parent] || Puppet::Parameter,
:attributes => options[:attributes],
:block => block,
:prefix => "Parameter",
:array => @parameters,
:hash => @paramhash
)
handle_param_options(name, options)
# Grr.
param.required_features = options[:required_features] if options[:required_features]
param.isnamevar if options[:namevar]
param
end
def self.newstate(name, options = {}, &block)
Puppet.warning "newstate() has been deprecrated; use newproperty(#{name})"
newproperty(name, options, &block)
end
# Create a new property. The first parameter must be the name of the property;
# this is how users will refer to the property when creating new instances.
# The second parameter is a hash of options; the options are:
# * :parent: The parent class for the property. Defaults to Puppet::Property.
# * :retrieve: The method to call on the provider or @parent object (if
# the provider is not set) to retrieve the current value.
def self.newproperty(name, options = {}, &block)
name = symbolize(name)
# This is here for types that might still have the old method of defining
# a parent class.
unless options.is_a? Hash
raise Puppet::DevError,
"Options must be a hash, not #{options.inspect}"
end
raise Puppet::DevError, "Class #{self.name} already has a property named #{name}" if @validproperties.include?(name)
if parent = options[:parent]
options.delete(:parent)
else
parent = Puppet::Property
end
# We have to create our own, new block here because we want to define
# an initial :retrieve method, if told to, and then eval the passed
# block if available.
prop = genclass(name, :parent => parent, :hash => @validproperties, :attributes => options) do
# If they've passed a retrieve method, then override the retrieve
# method on the class.
if options[:retrieve]
define_method(:retrieve) do
provider.send(options[:retrieve])
end
end
class_eval(&block) if block
end
# If it's the 'ensure' property, always put it first.
if name == :ensure
@properties.unshift prop
else
@properties << prop
end
prop
end
def self.paramdoc(param)
@paramhash[param].doc
end
# Return the parameter names
def self.parameters
return [] unless defined?(@parameters)
@parameters.collect { |klass| klass.name }
end
# Find the parameter class associated with a given parameter name.
def self.paramclass(name)
@paramhash[name]
end
# Return the property class associated with a name
def self.propertybyname(name)
@validproperties[name]
end
def self.validattr?(name)
name = symbolize(name)
return true if name == :name
@validattrs ||= {}
unless @validattrs.include?(name)
@validattrs[name] = !!(self.validproperty?(name) or self.validparameter?(name) or self.metaparam?(name))
end
@validattrs[name]
end
# does the name reflect a valid property?
def self.validproperty?(name)
name = symbolize(name)
@validproperties.include?(name) && @validproperties[name]
end
# Return the list of validproperties
def self.validproperties
return {} unless defined?(@parameters)
@validproperties.keys
end
# does the name reflect a valid parameter?
def self.validparameter?(name)
raise Puppet::DevError, "Class #{self} has not defined parameters" unless defined?(@parameters)
!!(@paramhash.include?(name) or @@metaparamhash.include?(name))
end
# This is a forward-compatibility method - it's the validity interface we'll use in Puppet::Resource.
def self.valid_parameter?(name)
validattr?(name)
end
# Return either the attribute alias or the attribute.
def attr_alias(name)
name = symbolize(name)
if synonym = self.class.attr_alias(name)
return synonym
else
return name
end
end
# Are we deleting this resource?
def deleting?
obj = @parameters[:ensure] and obj.should == :absent
end
# Create a new property if it is valid but doesn't exist
# Returns: true if a new parameter was added, false otherwise
def add_property_parameter(prop_name)
if self.class.validproperty?(prop_name) && !@parameters[prop_name]
self.newattr(prop_name)
return true
end
false
end
#
# The name_var is the key_attribute in the case that there is only one.
#
def name_var
key_attributes = self.class.key_attributes
(key_attributes.length == 1) && key_attributes.first
end
# abstract accessing parameters and properties, and normalize
# access to always be symbols, not strings
# This returns a value, not an object. It returns the 'is'
# value, but you can also specifically return 'is' and 'should'
# values using 'object.is(:property)' or 'object.should(:property)'.
def [](name)
name = attr_alias(name)
fail("Invalid parameter #{name}(#{name.inspect})") unless self.class.validattr?(name)
if name == :name
name = name_var
end
if obj = @parameters[name]
# Note that if this is a property, then the value is the "should" value,
# not the current value.
obj.value
else
return nil
end
end
# Abstract setting parameters and properties, and normalize
# access to always be symbols, not strings. This sets the 'should'
# value on properties, and otherwise just sets the appropriate parameter.
def []=(name,value)
name = attr_alias(name)
fail("Invalid parameter #{name}") unless self.class.validattr?(name)
if name == :name
name = name_var
end
raise Puppet::Error.new("Got nil value for #{name}") if value.nil?
property = self.newattr(name)
begin
# make sure the parameter doesn't have any errors
property.value = value
rescue => detail
error = Puppet::Error.new("Parameter #{name} failed: #{detail}")
error.set_backtrace(detail.backtrace)
raise error
end
nil
end
# remove a property from the object; useful in testing or in cleanup
# when an error has been encountered
def delete(attr)
attr = symbolize(attr)
if @parameters.has_key?(attr)
@parameters.delete(attr)
else
raise Puppet::DevError.new("Undefined attribute '#{attr}' in #{self}")
end
end
# iterate across the existing properties
def eachproperty
# properties is a private method
properties.each { |property|
yield property
}
end
# Create a transaction event. Called by Transaction or by
# a property.
def event(options = {})
Puppet::Transaction::Event.new({:resource => self, :file => file, :line => line, :tags => tags, :version => version}.merge(options))
end
# Let the catalog determine whether a given cached value is
# still valid or has expired.
def expirer
catalog
end
# retrieve the 'should' value for a specified property
def should(name)
name = attr_alias(name)
(prop = @parameters[name] and prop.is_a?(Puppet::Property)) ? prop.should : nil
end
# Create the actual attribute instance. Requires either the attribute
# name or class as the first argument, then an optional hash of
# attributes to set during initialization.
def newattr(name)
if name.is_a?(Class)
klass = name
name = klass.name
end
unless klass = self.class.attrclass(name)
raise Puppet::Error, "Resource type #{self.class.name} does not support parameter #{name}"
end
return @parameters[name] if @parameters.include?(name)
@parameters[name] = klass.new(:resource => self)
end
# return the value of a parameter
def parameter(name)
@parameters[name.to_sym]
end
def parameters
@parameters.dup
end
# Is the named property defined?
def propertydefined?(name)
name = name.intern unless name.is_a? Symbol
@parameters.include?(name)
end
# Return an actual property instance by name; to return the value, use 'resource[param]'
# LAK:NOTE(20081028) Since the 'parameter' method is now a superset of this method,
# this one should probably go away at some point.
def property(name)
(obj = @parameters[symbolize(name)] and obj.is_a?(Puppet::Property)) ? obj : nil
end
# For any parameters or properties that have defaults and have not yet been
# set, set them now. This method can be handed a list of attributes,
# and if so it will only set defaults for those attributes.
def set_default(attr)
return unless klass = self.class.attrclass(attr)
return unless klass.method_defined?(:default)
return if @parameters.include?(klass.name)
return unless parameter = newattr(klass.name)
if value = parameter.default and ! value.nil?
parameter.value = value
else
@parameters.delete(parameter.name)
end
end
# Convert our object to a hash. This just includes properties.
def to_hash
rethash = {}
@parameters.each do |name, obj|
rethash[name] = obj.value
end
rethash
end
def type
self.class.name
end
# Return a specific value for an attribute.
def value(name)
name = attr_alias(name)
(obj = @parameters[name] and obj.respond_to?(:value)) ? obj.value : nil
end
def version
return 0 unless catalog
catalog.version
end
# Return all of the property objects, in the order specified in the
# class.
def properties
self.class.properties.collect { |prop| @parameters[prop.name] }.compact
end
# Is this type's name isomorphic with the object? That is, if the
# name conflicts, does it necessarily mean that the objects conflict?
# Defaults to true.
def self.isomorphic?
if defined?(@isomorphic)
return @isomorphic
else
return true
end
end
def isomorphic?
self.class.isomorphic?
end
# is the instance a managed instance? A 'yes' here means that
# the instance was created from the language, vs. being created
# in order resolve other questions, such as finding a package
# in a list
def managed?
# Once an object is managed, it always stays managed; but an object
# that is listed as unmanaged might become managed later in the process,
# so we have to check that every time
if @managed
return @managed
else
@managed = false
properties.each { |property|
s = property.should
if s and ! property.class.unmanaged
@managed = true
break
end
}
return @managed
end
end
###############################
# Code related to the container behaviour.
# this is a retarded hack method to get around the difference between
# component children and file children
def self.depthfirst?
@depthfirst
end
def depthfirst?
self.class.depthfirst?
end
# Remove an object. The argument determines whether the object's
# subscriptions get eliminated, too.
def remove(rmdeps = true)
# This is hackish (mmm, cut and paste), but it works for now, and it's
# better than warnings.
@parameters.each do |name, obj|
obj.remove
end
@parameters.clear
@parent = nil
# Remove the reference to the provider.
if self.provider
@provider.clear
@provider = nil
end
end
###############################
# Code related to evaluating the resources.
# Flush the provider, if it supports it. This is called by the
# transaction.
def flush
self.provider.flush if self.provider and self.provider.respond_to?(:flush)
end
# if all contained objects are in sync, then we're in sync
# FIXME I don't think this is used on the type instances any more,
# it's really only used for testing
def insync?(is)
insync = true
if property = @parameters[:ensure]
unless is.include? property
raise Puppet::DevError,
"The is value is not in the is array for '#{property.name}'"
end
ensureis = is[property]
if property.insync?(ensureis) and property.should == :absent
return true
end
end
properties.each { |property|
unless is.include? property
raise Puppet::DevError,
"The is value is not in the is array for '#{property.name}'"
end
propis = is[property]
unless property.insync?(propis)
property.debug("Not in sync: #{propis.inspect} vs #{property.should.inspect}")
insync = false
#else
# property.debug("In sync")
end
}
#self.debug("#{self} sync status is #{insync}")
insync
end
# retrieve the current value of all contained properties
def retrieve
fail "Provider #{provider.class.name} is not functional on this host" if self.provider.is_a?(Puppet::Provider) and ! provider.class.suitable?
result = Puppet::Resource.new(type, title)
# Provide the name, so we know we'll always refer to a real thing
result[:name] = self[:name] unless self[:name] == title
if ensure_prop = property(:ensure) or (self.class.validattr?(:ensure) and ensure_prop = newattr(:ensure))
result[:ensure] = ensure_state = ensure_prop.retrieve
else
ensure_state = nil
end
properties.each do |property|
next if property.name == :ensure
if ensure_state == :absent
result[property] = :absent
else
result[property] = property.retrieve
end
end
result
end
def retrieve_resource
resource = retrieve
resource = Resource.new(type, title, :parameters => resource) if resource.is_a? Hash
resource
end
# Get a hash of the current properties. Returns a hash with
# the actual property instance as the key and the current value
# as the, um, value.
def currentpropvalues
# It's important to use the 'properties' method here, as it follows the order
# in which they're defined in the class. It also guarantees that 'ensure'
# is the first property, which is important for skipping 'retrieve' on
# all the properties if the resource is absent.
ensure_state = false
return properties.inject({}) do | prophash, property|
if property.name == :ensure
ensure_state = property.retrieve
prophash[property] = ensure_state
else
if ensure_state == :absent
prophash[property] = :absent
else
prophash[property] = property.retrieve
end
end
prophash
end
end
# Are we running in noop mode?
def noop?
# If we're not a host_config, we're almost certainly part of
# Settings, and we want to ignore 'noop'
return false if catalog and ! catalog.host_config?
if defined?(@noop)
@noop
else
Puppet[:noop]
end
end
def noop
noop?
end
###############################
# Code related to managing resource instances.
require 'puppet/transportable'
# retrieve a named instance of the current type
def self.[](name)
raise "Global resource access is deprecated"
@objects[name] || @aliases[name]
end
# add an instance by name to the class list of instances
def self.[]=(name,object)
raise "Global resource storage is deprecated"
newobj = nil
if object.is_a?(Puppet::Type)
newobj = object
else
raise Puppet::DevError, "must pass a Puppet::Type object"
end
if exobj = @objects[name] and self.isomorphic?
msg = "Object '#{newobj.class.name}[#{name}]' already exists"
msg += ("in file #{object.file} at line #{object.line}") if exobj.file and exobj.line
msg += ("and cannot be redefined in file #{object.file} at line #{object.line}") if object.file and object.line
error = Puppet::Error.new(msg)
raise error
else
#Puppet.info("adding %s of type %s to class list" %
# [name,object.class])
@objects[name] = newobj
end
end
# Create an alias. We keep these in a separate hash so that we don't encounter
# the objects multiple times when iterating over them.
def self.alias(name, obj)
raise "Global resource aliasing is deprecated"
if @objects.include?(name)
unless @objects[name] == obj
raise Puppet::Error.new(
"Cannot create alias #{name}: object already exists"
)
end
end
if @aliases.include?(name)
unless @aliases[name] == obj
raise Puppet::Error.new(
"Object #{@aliases[name].name} already has alias #{name}"
)
end
end
@aliases[name] = obj
end
# remove all of the instances of a single type
def self.clear
raise "Global resource removal is deprecated"
if defined?(@objects)
@objects.each do |name, obj|
obj.remove(true)
end
@objects.clear
end
@aliases.clear if defined?(@aliases)
end
# Force users to call this, so that we can merge objects if
# necessary.
def self.create(args)
# LAK:DEP Deprecation notice added 12/17/2008
Puppet.warning "Puppet::Type.create is deprecated; use Puppet::Type.new"
new(args)
end
# remove a specified object
def self.delete(resource)
raise "Global resource removal is deprecated"
return unless defined?(@objects)
@objects.delete(resource.title) if @objects.include?(resource.title)
@aliases.delete(resource.title) if @aliases.include?(resource.title)
if @aliases.has_value?(resource)
names = []
@aliases.each do |name, otherres|
if otherres == resource
names << name
end
end
names.each { |name| @aliases.delete(name) }
end
end
# iterate across each of the type's instances
def self.each
raise "Global resource iteration is deprecated"
return unless defined?(@objects)
@objects.each { |name,instance|
yield instance
}
end
# does the type have an object with the given name?
def self.has_key?(name)
raise "Global resource access is deprecated"
@objects.has_key?(name)
end
# Retrieve all known instances. Either requires providers or must be overridden.
def self.instances
raise Puppet::DevError, "#{self.name} has no providers and has not overridden 'instances'" if provider_hash.empty?
# Put the default provider first, then the rest of the suitable providers.
provider_instances = {}
providers_by_source.collect do |provider|
provider.instances.collect do |instance|
# We always want to use the "first" provider instance we find, unless the resource
# is already managed and has a different provider set
if other = provider_instances[instance.name]
Puppet.warning "%s %s found in both %s and %s; skipping the %s version" %
[self.name.to_s.capitalize, instance.name, other.class.name, instance.class.name, instance.class.name]
next
end
provider_instances[instance.name] = instance
new(:name => instance.name, :provider => instance, :audit => :all)
end
end.flatten.compact
end
# Return a list of one suitable provider per source, with the default provider first.
def self.providers_by_source
# Put the default provider first, then the rest of the suitable providers.
sources = []
[defaultprovider, suitableprovider].flatten.uniq.collect do |provider|
next if sources.include?(provider.source)
sources << provider.source
provider
end.compact
end
# Convert a simple hash into a Resource instance.
def self.hash2resource(hash)
hash = hash.inject({}) { |result, ary| result[ary[0].to_sym] = ary[1]; result }
title = hash.delete(:title)
title ||= hash[:name]
title ||= hash[key_attributes.first] if key_attributes.length == 1
raise Puppet::Error, "Title or name must be provided" unless title
# Now create our resource.
resource = Puppet::Resource.new(self.name, title)
[:catalog].each do |attribute|
if value = hash[attribute]
hash.delete(attribute)
resource.send(attribute.to_s + "=", value)
end
end
hash.each do |param, value|
resource[param] = value
end
resource
end
# Create the path for logging and such.
def pathbuilder
if p = parent
[p.pathbuilder, self.ref].flatten
else
[self.ref]
end
end
###############################
# Add all of the meta parameters.
newmetaparam(:noop) do
desc "Boolean flag indicating whether work should actually
be done."
newvalues(:true, :false)
munge do |value|
case value
when true, :true, "true"; @resource.noop = true
when false, :false, "false"; @resource.noop = false
end
end
end
newmetaparam(:schedule) do
desc "On what schedule the object should be managed. You must create a
schedule object, and then reference the name of that object to use
- that for your schedule::
+ that for your schedule:
- schedule { daily:
- period => daily,
- range => \"2-4\"
- }
+ schedule { daily:
+ period => daily,
+ range => \"2-4\"
+ }
- exec { \"/usr/bin/apt-get update\":
- schedule => daily
- }
+ exec { \"/usr/bin/apt-get update\":
+ schedule => daily
+ }
The creation of the schedule object does not need to appear in the
configuration before objects that use it."
end
newmetaparam(:audit) do
desc "Audit specified attributes of resources over time, and report if any have changed.
This attribute can be used to track changes to any resource over time, and can
provide an audit trail of every change that happens on any given machine.
Note that you cannot both audit and manage an attribute - managing it guarantees
the value, and any changes already get logged."
validate do |list|
list = Array(list)
unless list == [:all]
list.each do |param|
next if @resource.class.validattr?(param)
fail "Cannot audit #{param}: not a valid attribute for #{resource}"
end
end
end
munge do |args|
properties_to_audit(args).each do |param|
next unless resource.class.validproperty?(param)
resource.newattr(param)
end
end
def all_properties
resource.class.properties.find_all do |property|
resource.provider.nil? or resource.provider.class.supports_parameter?(property)
end.collect do |property|
property.name
end
end
def properties_to_audit(list)
if list == :all
list = all_properties if list == :all
else
list = Array(list).collect { |p| p.to_sym }
end
end
end
newmetaparam(:check) do
desc "Audit specified attributes of resources over time, and report if any have changed.
This parameter has been deprecated in favor of 'audit'."
munge do |args|
resource.warning "'check' attribute is deprecated; use 'audit' instead"
resource[:audit] = args
end
end
newmetaparam(:loglevel) do
desc "Sets the level that information will be logged.
The log levels have the biggest impact when logs are sent to
syslog (which is currently the default)."
defaultto :notice
newvalues(*Puppet::Util::Log.levels)
newvalues(:verbose)
munge do |loglevel|
val = super(loglevel)
if val == :verbose
val = :info
end
val
end
end
newmetaparam(:alias) do
desc "Creates an alias for the object. Puppet uses this internally when you
- provide a symbolic name::
-
- file { sshdconfig:
- path => $operatingsystem ? {
- solaris => \"/usr/local/etc/ssh/sshd_config\",
- default => \"/etc/ssh/sshd_config\"
- },
- source => \"...\"
- }
+ provide a symbolic name:
+
+ file { sshdconfig:
+ path => $operatingsystem ? {
+ solaris => \"/usr/local/etc/ssh/sshd_config\",
+ default => \"/etc/ssh/sshd_config\"
+ },
+ source => \"...\"
+ }
- service { sshd:
- subscribe => file[sshdconfig]
- }
+ service { sshd:
+ subscribe => file[sshdconfig]
+ }
- When you use this feature, the parser sets ``sshdconfig`` as the name,
+ When you use this feature, the parser sets `sshdconfig` as the name,
and the library sets that as an alias for the file so the dependency
- lookup for ``sshd`` works. You can use this parameter yourself,
+ lookup for `sshd` works. You can use this parameter yourself,
but note that only the library can use these aliases; for instance,
- the following code will not work::
+ the following code will not work:
- file { \"/etc/ssh/sshd_config\":
- owner => root,
- group => root,
- alias => sshdconfig
- }
+ file { \"/etc/ssh/sshd_config\":
+ owner => root,
+ group => root,
+ alias => sshdconfig
+ }
- file { sshdconfig:
- mode => 644
- }
+ file { sshdconfig:
+ mode => 644
+ }
There's no way here for the Puppet parser to know that these two stanzas
should be affecting the same file.
- See the `LanguageTutorial language tutorial`:trac: for more information.
+ See the [Language Tutorial](http://docs.puppetlabs.com/guides/language_tutorial.html) for more information.
"
munge do |aliases|
aliases = [aliases] unless aliases.is_a?(Array)
raise(ArgumentError, "Cannot add aliases without a catalog") unless @resource.catalog
aliases.each do |other|
if obj = @resource.catalog.resource(@resource.class.name, other)
unless obj.object_id == @resource.object_id
self.fail("#{@resource.title} can not create alias #{other}: object already exists")
end
next
end
# Newschool, add it to the catalog.
@resource.catalog.alias(@resource, other)
end
end
end
newmetaparam(:tag) do
desc "Add the specified tags to the associated resource. While all resources
are automatically tagged with as much information as possible
(e.g., each class and definition containing the resource), it can
be useful to add your own tags to a given resource.
Tags are currently useful for things like applying a subset of a
- host's configuration::
+ host's configuration:
- puppet agent --test --tags mytag
+ puppet agent --test --tags mytag
This way, when you're testing a configuration you can run just the
portion you're testing."
munge do |tags|
tags = [tags] unless tags.is_a? Array
tags.each do |tag|
@resource.tag(tag)
end
end
end
class RelationshipMetaparam < Puppet::Parameter
class << self
attr_accessor :direction, :events, :callback, :subclasses
end
@subclasses = []
def self.inherited(sub)
@subclasses << sub
end
def munge(references)
references = [references] unless references.is_a?(Array)
references.collect do |ref|
if ref.is_a?(Puppet::Resource)
ref
else
Puppet::Resource.new(ref)
end
end
end
def validate_relationship
@value.each do |ref|
unless @resource.catalog.resource(ref.to_s)
description = self.class.direction == :in ? "dependency" : "dependent"
fail "Could not find #{description} #{ref} for #{resource.ref}"
end
end
end
# Create edges from each of our relationships. :in
# relationships are specified by the event-receivers, and :out
# relationships are specified by the event generator. This
# way 'source' and 'target' are consistent terms in both edges
# and events -- that is, an event targets edges whose source matches
# the event's source. The direction of the relationship determines
# which resource is applied first and which resource is considered
# to be the event generator.
def to_edges
@value.collect do |reference|
reference.catalog = resource.catalog
# Either of the two retrieval attempts could have returned
# nil.
unless related_resource = reference.resolve
self.fail "Could not retrieve dependency '#{reference}' of #{@resource.ref}"
end
# Are we requiring them, or vice versa? See the method docs
# for futher info on this.
if self.class.direction == :in
source = related_resource
target = @resource
else
source = @resource
target = related_resource
end
if method = self.class.callback
subargs = {
:event => self.class.events,
:callback => method
}
self.debug("subscribes to #{related_resource.ref}")
else
# If there's no callback, there's no point in even adding
# a label.
subargs = nil
self.debug("requires #{related_resource.ref}")
end
rel = Puppet::Relationship.new(source, target, subargs)
end
end
end
def self.relationship_params
RelationshipMetaparam.subclasses
end
# Note that the order in which the relationships params is defined
# matters. The labelled params (notify and subcribe) must be later,
# so that if both params are used, those ones win. It's a hackish
# solution, but it works.
newmetaparam(:require, :parent => RelationshipMetaparam, :attributes => {:direction => :in, :events => :NONE}) do
desc "One or more objects that this object depends on.
This is used purely for guaranteeing that changes to required objects
- happen before the dependent object. For instance::
+ happen before the dependent object. For instance:
- # Create the destination directory before you copy things down
- file { \"/usr/local/scripts\":
- ensure => directory
- }
+ # Create the destination directory before you copy things down
+ file { \"/usr/local/scripts\":
+ ensure => directory
+ }
- file { \"/usr/local/scripts/myscript\":
- source => \"puppet://server/module/myscript\",
- mode => 755,
- require => File[\"/usr/local/scripts\"]
- }
+ file { \"/usr/local/scripts/myscript\":
+ source => \"puppet://server/module/myscript\",
+ mode => 755,
+ require => File[\"/usr/local/scripts\"]
+ }
Multiple dependencies can be specified by providing a comma-seperated list
- of resources, enclosed in square brackets::
+ of resources, enclosed in square brackets:
- require => [ File[\"/usr/local\"], File[\"/usr/local/scripts\"] ]
+ require => [ File[\"/usr/local\"], File[\"/usr/local/scripts\"] ]
Note that Puppet will autorequire everything that it can, and
there are hooks in place so that it's easy for resources to add new
ways to autorequire objects, so if you think Puppet could be
smarter here, let us know.
In fact, the above code was redundant -- Puppet will autorequire
any parent directories that are being managed; it will
automatically realize that the parent directory should be created
before the script is pulled down.
Currently, exec resources will autorequire their CWD (if it is
specified) plus any fully qualified paths that appear in the
- command. For instance, if you had an ``exec`` command that ran
- the ``myscript`` mentioned above, the above code that pulls the
+ command. For instance, if you had an `exec` command that ran
+ the `myscript` mentioned above, the above code that pulls the
file down would be automatically listed as a requirement to the
- ``exec`` code, so that you would always be running againts the
+ `exec` code, so that you would always be running againts the
most recent version.
"
end
newmetaparam(:subscribe, :parent => RelationshipMetaparam, :attributes => {:direction => :in, :events => :ALL_EVENTS, :callback => :refresh}) do
desc "One or more objects that this object depends on. Changes in the
subscribed to objects result in the dependent objects being
- refreshed (e.g., a service will get restarted). For instance::
-
- class nagios {
- file { \"/etc/nagios/nagios.conf\":
- source => \"puppet://server/module/nagios.conf\",
- alias => nagconf # just to make things easier for me
+ refreshed (e.g., a service will get restarted). For instance:
+
+ class nagios {
+ file { \"/etc/nagios/nagios.conf\":
+ source => \"puppet://server/module/nagios.conf\",
+ alias => nagconf # just to make things easier for me
+ }
+ service { nagios:
+ ensure => running,
+ subscribe => File[nagconf]
+ }
}
- service { nagios:
- ensure => running,
- subscribe => File[nagconf]
- }
- }
- Currently the ``exec``, ``mount`` and ``service`` type support
+ Currently the `exec`, `mount` and `service` type support
refreshing.
"
end
newmetaparam(:before, :parent => RelationshipMetaparam, :attributes => {:direction => :out, :events => :NONE}) do
desc %{This parameter is the opposite of **require** -- it guarantees
that the specified object is applied later than the specifying
- object::
+ object:
- file { "/var/nagios/configuration":
- source => "...",
- recurse => true,
- before => Exec["nagios-rebuid"]
- }
+ file { "/var/nagios/configuration":
+ source => "...",
+ recurse => true,
+ before => Exec["nagios-rebuid"]
+ }
- exec { "nagios-rebuild":
- command => "/usr/bin/make",
- cwd => "/var/nagios/configuration"
- }
+ exec { "nagios-rebuild":
+ command => "/usr/bin/make",
+ cwd => "/var/nagios/configuration"
+ }
This will make sure all of the files are up to date before the
make command is run.}
end
newmetaparam(:notify, :parent => RelationshipMetaparam, :attributes => {:direction => :out, :events => :ALL_EVENTS, :callback => :refresh}) do
desc %{This parameter is the opposite of **subscribe** -- it sends events
- to the specified object::
+ to the specified object:
- file { "/etc/sshd_config":
- source => "....",
- notify => Service[sshd]
- }
+ file { "/etc/sshd_config":
+ source => "....",
+ notify => Service[sshd]
+ }
- service { sshd:
- ensure => running
- }
+ service { sshd:
+ ensure => running
+ }
This will restart the sshd service if the sshd config file changes.}
end
newmetaparam(:stage) do
desc %{Which run stage a given resource should reside in. This just creates
a dependency on or from the named milestone. For instance, saying that
this is in the 'bootstrap' stage creates a dependency on the 'bootstrap'
milestone.
By default, all classes get directly added to the
'main' stage. You can create new stages as resources:
- stage { [pre, post]: }
+ stage { [pre, post]: }
To order stages, use standard relationships:
- stage { pre: before => Stage[main] }
+ stage { pre: before => Stage[main] }
Or use the new relationship syntax:
- Stage[pre] -> Stage[main] -> Stage[post]
+ Stage[pre] -> Stage[main] -> Stage[post]
Then use the new class parameters to specify a stage:
- class { foo: stage => pre }
+ class { foo: stage => pre }
Stages can only be set on classes, not individual resources. This will
- fail::
+ fail:
- file { '/foo': stage => pre, ensure => file }
+ file { '/foo': stage => pre, ensure => file }
}
end
###############################
# All of the provider plumbing for the resource types.
require 'puppet/provider'
require 'puppet/util/provider_features'
# Add the feature handling module.
extend Puppet::Util::ProviderFeatures
attr_reader :provider
# the Type class attribute accessors
class << self
attr_accessor :providerloader
attr_writer :defaultprovider
end
# Find the default provider.
def self.defaultprovider
unless @defaultprovider
suitable = suitableprovider
# Find which providers are a default for this system.
defaults = suitable.find_all { |provider| provider.default? }
# If we don't have any default we use suitable providers
defaults = suitable if defaults.empty?
max = defaults.collect { |provider| provider.specificity }.max
defaults = defaults.find_all { |provider| provider.specificity == max }
retval = nil
if defaults.length > 1
Puppet.warning(
"Found multiple default providers for #{self.name}: #{defaults.collect { |i| i.name.to_s }.join(", ")}; using #{defaults[0].name}"
)
retval = defaults.shift
elsif defaults.length == 1
retval = defaults.shift
else
raise Puppet::DevError, "Could not find a default provider for #{self.name}"
end
@defaultprovider = retval
end
@defaultprovider
end
def self.provider_hash_by_type(type)
@provider_hashes ||= {}
@provider_hashes[type] ||= {}
end
def self.provider_hash
Puppet::Type.provider_hash_by_type(self.name)
end
# Retrieve a provider by name.
def self.provider(name)
name = Puppet::Util.symbolize(name)
# If we don't have it yet, try loading it.
@providerloader.load(name) unless provider_hash.has_key?(name)
provider_hash[name]
end
# Just list all of the providers.
def self.providers
provider_hash.keys
end
def self.validprovider?(name)
name = Puppet::Util.symbolize(name)
(provider_hash.has_key?(name) && provider_hash[name].suitable?)
end
# Create a new provider of a type. This method must be called
# directly on the type that it's implementing.
def self.provide(name, options = {}, &block)
name = Puppet::Util.symbolize(name)
if obj = provider_hash[name]
Puppet.debug "Reloading #{name} #{self.name} provider"
unprovide(name)
end
parent = if pname = options[:parent]
options.delete(:parent)
if pname.is_a? Class
pname
else
if provider = self.provider(pname)
provider
else
raise Puppet::DevError,
"Could not find parent provider #{pname} of #{name}"
end
end
else
Puppet::Provider
end
options[:resource_type] ||= self
self.providify
provider = genclass(
name,
:parent => parent,
:hash => provider_hash,
:prefix => "Provider",
:block => block,
:include => feature_module,
:extend => feature_module,
:attributes => options
)
provider
end
# Make sure we have a :provider parameter defined. Only gets called if there
# are providers.
def self.providify
return if @paramhash.has_key? :provider
newparam(:provider) do
desc "The specific backend for #{self.name.to_s} to use. You will
seldom need to specify this -- Puppet will usually discover the
appropriate provider for your platform."
# This is so we can refer back to the type to get a list of
# providers for documentation.
class << self
attr_accessor :parenttype
end
# We need to add documentation for each provider.
def self.doc
@doc + " Available providers are:\n\n" + parenttype.providers.sort { |a,b|
a.to_s <=> b.to_s
}.collect { |i|
"* **#{i}**: #{parenttype().provider(i).doc}"
}.join("\n")
end
defaultto {
@resource.class.defaultprovider.name
}
validate do |provider_class|
provider_class = provider_class[0] if provider_class.is_a? Array
provider_class = provider_class.class.name if provider_class.is_a?(Puppet::Provider)
unless provider = @resource.class.provider(provider_class)
raise ArgumentError, "Invalid #{@resource.class.name} provider '#{provider_class}'"
end
end
munge do |provider|
provider = provider[0] if provider.is_a? Array
provider = provider.intern if provider.is_a? String
@resource.provider = provider
if provider.is_a?(Puppet::Provider)
provider.class.name
else
provider
end
end
end.parenttype = self
end
def self.unprovide(name)
if provider_hash.has_key? name
rmclass(
name,
:hash => provider_hash,
:prefix => "Provider"
)
if @defaultprovider and @defaultprovider.name == name
@defaultprovider = nil
end
end
end
# Return an array of all of the suitable providers.
def self.suitableprovider
providerloader.loadall if provider_hash.empty?
provider_hash.find_all { |name, provider|
provider.suitable?
}.collect { |name, provider|
provider
}.reject { |p| p.name == :fake } # For testing
end
def provider=(name)
if name.is_a?(Puppet::Provider)
@provider = name
@provider.resource = self
elsif klass = self.class.provider(name)
@provider = klass.new(self)
else
raise ArgumentError, "Could not find #{name} provider of #{self.class.name}"
end
end
###############################
# All of the relationship code.
# Specify a block for generating a list of objects to autorequire. This
# makes it so that you don't have to manually specify things that you clearly
# require.
def self.autorequire(name, &block)
@autorequires ||= {}
@autorequires[name] = block
end
# Yield each of those autorequires in turn, yo.
def self.eachautorequire
@autorequires ||= {}
@autorequires.each { |type, block|
yield(type, block)
}
end
# Figure out of there are any objects we can automatically add as
# dependencies.
def autorequire(rel_catalog = nil)
rel_catalog ||= catalog
raise(Puppet::DevError, "You cannot add relationships without a catalog") unless rel_catalog
reqs = []
self.class.eachautorequire { |type, block|
# Ignore any types we can't find, although that would be a bit odd.
next unless typeobj = Puppet::Type.type(type)
# Retrieve the list of names from the block.
next unless list = self.instance_eval(&block)
list = [list] unless list.is_a?(Array)
# Collect the current prereqs
list.each { |dep|
obj = nil
# Support them passing objects directly, to save some effort.
unless dep.is_a? Puppet::Type
# Skip autorequires that we aren't managing
unless dep = rel_catalog.resource(type, dep)
next
end
end
reqs << Puppet::Relationship.new(dep, self)
}
}
reqs
end
# Build the dependencies associated with an individual object.
def builddepends
# Handle the requires
self.class.relationship_params.collect do |klass|
if param = @parameters[klass.name]
param.to_edges
end
end.flatten.reject { |r| r.nil? }
end
# Define the initial list of tags.
def tags=(list)
tag(self.class.name)
tag(*list)
end
# Types (which map to resources in the languages) are entirely composed of
# attribute value pairs. Generally, Puppet calls any of these things an
# 'attribute', but these attributes always take one of three specific
# forms: parameters, metaparams, or properties.
# In naming methods, I have tried to consistently name the method so
# that it is clear whether it operates on all attributes (thus has 'attr' in
# the method name, or whether it operates on a specific type of attributes.
attr_writer :title
attr_writer :noop
include Enumerable
# class methods dealing with Type management
public
# the Type class attribute accessors
class << self
attr_reader :name
attr_accessor :self_refresh
include Enumerable, Puppet::Util::ClassGen
include Puppet::MetaType::Manager
include Puppet::Util
include Puppet::Util::Logging
end
# all of the variables that must be initialized for each subclass
def self.initvars
# all of the instances of this class
@objects = Hash.new
@aliases = Hash.new
@defaults = {}
@parameters ||= []
@validproperties = {}
@properties = []
@parameters = []
@paramhash = {}
@attr_aliases = {}
@paramdoc = Hash.new { |hash,key|
key = key.intern if key.is_a?(String)
if hash.include?(key)
hash[key]
else
"Param Documentation for #{key} not found"
end
}
@doc ||= ""
end
def self.to_s
if defined?(@name)
"Puppet::Type::#{@name.to_s.capitalize}"
else
super
end
end
# Create a block to validate that our object is set up entirely. This will
# be run before the object is operated on.
def self.validate(&block)
define_method(:validate, &block)
#@validate = block
end
# The catalog that this resource is stored in.
attr_accessor :catalog
# is the resource exported
attr_accessor :exported
# is the resource virtual (it should not :-))
attr_accessor :virtual
# create a log at specified level
def log(msg)
Puppet::Util::Log.create(
:level => @parameters[:loglevel].value,
:message => msg,
:source => self
)
end
# instance methods related to instance intrinsics
# e.g., initialize and name
public
attr_reader :original_parameters
# initialize the type instance
def initialize(resource)
raise Puppet::DevError, "Got TransObject instead of Resource or hash" if resource.is_a?(Puppet::TransObject)
resource = self.class.hash2resource(resource) unless resource.is_a?(Puppet::Resource)
# The list of parameter/property instances.
@parameters = {}
# Set the title first, so any failures print correctly.
if resource.type.to_s.downcase.to_sym == self.class.name
self.title = resource.title
else
# This should only ever happen for components
self.title = resource.ref
end
[:file, :line, :catalog, :exported, :virtual].each do |getter|
setter = getter.to_s + "="
if val = resource.send(getter)
self.send(setter, val)
end
end
@tags = resource.tags
@original_parameters = resource.to_hash
set_name(@original_parameters)
set_default(:provider)
set_parameters(@original_parameters)
self.validate if self.respond_to?(:validate)
end
private
# Set our resource's name.
def set_name(hash)
self[name_var] = hash.delete(name_var) if name_var
end
# Set all of the parameters from a hash, in the appropriate order.
def set_parameters(hash)
# Use the order provided by allattrs, but add in any
# extra attributes from the resource so we get failures
# on invalid attributes.
no_values = []
(self.class.allattrs + hash.keys).uniq.each do |attr|
begin
# Set any defaults immediately. This is mostly done so
# that the default provider is available for any other
# property validation.
if hash.has_key?(attr)
self[attr] = hash[attr]
else
no_values << attr
end
rescue ArgumentError, Puppet::Error, TypeError
raise
rescue => detail
error = Puppet::DevError.new( "Could not set #{attr} on #{self.class.name}: #{detail}")
error.set_backtrace(detail.backtrace)
raise error
end
end
no_values.each do |attr|
set_default(attr)
end
end
public
# Set up all of our autorequires.
def finish
# Make sure all of our relationships are valid. Again, must be done
# when the entire catalog is instantiated.
self.class.relationship_params.collect do |klass|
if param = @parameters[klass.name]
param.validate_relationship
end
end.flatten.reject { |r| r.nil? }
end
# For now, leave the 'name' method functioning like it used to. Once 'title'
# works everywhere, I'll switch it.
def name
self[:name]
end
# Look up our parent in the catalog, if we have one.
def parent
return nil unless catalog
unless defined?(@parent)
if parents = catalog.adjacent(self, :direction => :in)
# We should never have more than one parent, so let's just ignore
# it if we happen to.
@parent = parents.shift
else
@parent = nil
end
end
@parent
end
# Return the "type[name]" style reference.
def ref
"#{self.class.name.to_s.capitalize}[#{self.title}]"
end
def self_refresh?
self.class.self_refresh
end
# Mark that we're purging.
def purging
@purging = true
end
# Is this resource being purged? Used by transactions to forbid
# deletion when there are dependencies.
def purging?
if defined?(@purging)
@purging
else
false
end
end
# Retrieve the title of an object. If no title was set separately,
# then use the object's name.
def title
unless @title
if self.class.validparameter?(name_var)
@title = self[:name]
elsif self.class.validproperty?(name_var)
@title = self.should(name_var)
else
self.devfail "Could not find namevar #{name_var} for #{self.class.name}"
end
end
@title
end
# convert to a string
def to_s
self.ref
end
# Convert to a transportable object
def to_trans(ret = true)
trans = TransObject.new(self.title, self.class.name)
values = retrieve_resource
values.each do |name, value|
name = name.name if name.respond_to? :name
trans[name] = value
end
@parameters.each do |name, param|
# Avoid adding each instance name twice
next if param.class.isnamevar? and param.value == self.title
# We've already got property values
next if param.is_a?(Puppet::Property)
trans[name] = param.value
end
trans.tags = self.tags
# FIXME I'm currently ignoring 'parent' and 'path'
trans
end
def to_resource
# this 'type instance' versus 'resource' distinction seems artificial
# I'd like to see it collapsed someday ~JW
self.to_trans.to_resource
end
def virtual?; !!@virtual; end
def exported?; !!@exported; end
end
end
require 'puppet/provider'
# Always load these types.
require 'puppet/type/component'
diff --git a/lib/puppet/type/zone.rb b/lib/puppet/type/zone.rb
index 5265b8a0f..408d6f5dd 100644
--- a/lib/puppet/type/zone.rb
+++ b/lib/puppet/type/zone.rb
@@ -1,444 +1,444 @@
Puppet::Type.newtype(:zone) do
@doc = "Solaris zones."
# These properties modify the zone configuration, and they need to provide
# the text separately from syncing it, so all config statements can be rolled
# into a single creation statement.
class ZoneConfigProperty < Puppet::Property
# Perform the config operation.
def sync
provider.setconfig self.configtext
end
end
# Those properties that can have multiple instances.
class ZoneMultiConfigProperty < ZoneConfigProperty
def configtext
list = @should
current_value = self.retrieve
unless current_value.is_a? Symbol
if current_value.is_a? Array
list += current_value
else
list << current_value if current_value
end
end
# Some hackery so we can test whether current_value is an array or a symbol
if current_value.is_a? Array
tmpis = current_value
else
if current_value
tmpis = [current_value]
else
tmpis = []
end
end
rms = []
adds = []
# Collect the modifications to make
list.sort.uniq.collect do |obj|
# Skip objectories that are configured and should be
next if tmpis.include?(obj) and @should.include?(obj)
if tmpis.include?(obj)
rms << obj
else
adds << obj
end
end
# And then perform all of the removals before any of the adds.
(rms.collect { |o| rm(o) } + adds.collect { |o| add(o) }).join("\n")
end
# We want all specified directories to be included.
def insync?(current_value)
if current_value.is_a? Array and @should.is_a? Array
current_value.sort == @should.sort
else
current_value == @should
end
end
end
ensurable do
desc "The running state of the zone. The valid states directly reflect
the states that `zoneadm` provides. The states are linear,
in that a zone must be `configured` then `installed`, and
only then can be `running`. Note also that `halt` is currently
used to stop zones."
@states = {}
@parametervalues = []
def self.alias_state(values)
@state_aliases ||= {}
values.each do |nick, name|
@state_aliases[nick] = name
end
end
def self.newvalue(name, hash)
@parametervalues = [] if @parametervalues.is_a? Hash
@parametervalues << name
@states[name] = hash
hash[:name] = name
end
def self.state_name(name)
if other = @state_aliases[name]
other
else
name
end
end
newvalue :absent, :down => :destroy
newvalue :configured, :up => :configure, :down => :uninstall
newvalue :installed, :up => :install, :down => :stop
newvalue :running, :up => :start
alias_state :incomplete => :installed, :ready => :installed, :shutting_down => :running
defaultto :running
def self.state_index(value)
@parametervalues.index(state_name(value))
end
# Return all of the states between two listed values, exclusive
# of the first item.
def self.state_sequence(first, second)
findex = sindex = nil
unless findex = @parametervalues.index(state_name(first))
raise ArgumentError, "'#{first}' is not a valid zone state"
end
unless sindex = @parametervalues.index(state_name(second))
raise ArgumentError, "'#{first}' is not a valid zone state"
end
list = nil
# Apparently ranges are unidirectional, so we have to reverse
# the range op twice.
if findex > sindex
list = @parametervalues[sindex..findex].collect do |name|
@states[name]
end.reverse
else
list = @parametervalues[findex..sindex].collect do |name|
@states[name]
end
end
# The first result is the current state, so don't return it.
list[1..-1]
end
def retrieve
provider.properties[:ensure]
end
def sync
method = nil
if up?
direction = :up
else
direction = :down
end
# We need to get the state we're currently in and just call
# everything between it and us.
self.class.state_sequence(self.retrieve, self.should).each do |state|
if method = state[direction]
warned = false
while provider.processing?
unless warned
info "Waiting for zone to finish processing"
warned = true
end
sleep 1
end
provider.send(method)
else
raise Puppet::DevError, "Cannot move #{direction} from #{st[:name]}"
end
end
("zone_#{self.should}").intern
end
# Are we moving up the property tree?
def up?
current_value = self.retrieve
self.class.state_index(current_value) < self.class.state_index(self.should)
end
end
newparam(:name) do
desc "The name of the zone."
isnamevar
end
newparam(:id) do
desc "The numerical ID of the zone. This number is autogenerated
and cannot be changed."
end
newparam(:clone) do
desc "Instead of installing the zone, clone it from another zone.
If the zone root resides on a zfs file system, a snapshot will be
used to create the clone, is it redisides on ufs, a copy of the zone
will be used. The zone you clone from must not be running."
end
newproperty(:ip, :parent => ZoneMultiConfigProperty) do
require 'ipaddr'
desc "The IP address of the zone. IP addresses must be specified
with the interface, separated by a colon, e.g.: bge0:192.168.0.1.
For multiple interfaces, specify them in an array."
# Add an interface.
def add(str)
interface, ip, defrouter = ipsplit(str)
cmd = "add net\n"
cmd += "set physical=#{interface}\n" if interface
cmd += "set address=#{ip}\n" if ip
cmd += "set defrouter=#{defrouter}\n" if defrouter
#if @resource[:iptype] == :shared
cmd += "end\n"
end
# Convert a string into the component interface, address and defrouter
def ipsplit(str)
interface, address, defrouter = str.split(':')
return interface, address, defrouter
end
# Remove an interface.
def rm(str)
interface, ip, defrouter = ipsplit(str)
# Reality seems to disagree with the documentation here; the docs
# specify that braces are required, but they're apparently only
# required if you're specifying multiple values.
if ip
"remove net address=#{ip}"
elsif interface
"remove net interface=#{interface}"
else
raise ArgumentError, "can not remove network based on default router"
end
end
end
newproperty(:iptype, :parent => ZoneConfigProperty) do
desc "The IP stack type of the zone. Can either be 'shared' or 'exclusive'."
defaultto :shared
newvalue :shared
newvalue :exclusive
def configtext
"set ip-type=#{self.should}"
end
end
newproperty(:autoboot, :parent => ZoneConfigProperty) do
desc "Whether the zone should automatically boot."
defaultto true
newvalue(:true) {}
newvalue(:false) {}
def configtext
"set autoboot=#{self.should}"
end
end
newproperty(:pool, :parent => ZoneConfigProperty) do
desc "The resource pool for this zone."
def configtext
"set pool=#{self.should}"
end
end
newproperty(:shares, :parent => ZoneConfigProperty) do
desc "Number of FSS CPU shares allocated to the zone."
def configtext
"add rctl\nset name=zone.cpu-shares\nadd value (priv=privileged,limit=#{self.should},action=none)\nend"
end
end
newproperty(:inherit, :parent => ZoneMultiConfigProperty) do
desc "The list of directories that the zone inherits from the global
zone. All directories must be fully qualified."
validate do |value|
unless value =~ /^\//
raise ArgumentError, "Inherited filesystems must be fully qualified"
end
end
# Add a directory to our list of inherited directories.
def add(dir)
"add inherit-pkg-dir\nset dir=#{dir}\nend"
end
def rm(dir)
# Reality seems to disagree with the documentation here; the docs
# specify that braces are required, but they're apparently only
# required if you're specifying multiple values.
"remove inherit-pkg-dir dir=#{dir}"
end
def should
@should
end
end
# Specify the sysidcfg file. This is pretty hackish, because it's
# only used to boot the zone the very first time.
newparam(:sysidcfg) do
desc %{The text to go into the sysidcfg file when the zone is first
booted. The best way is to use a template:
# $templatedir/sysidcfg
system_locale=en_US
timezone=GMT
terminal=xterms
security_policy=NONE
- root_password=<%= password %>
+ root_password=<%= password %>
timeserver=localhost
- name_service=DNS {domain_name=<%= domain %> name_server=<%= nameserver %>}
- network_interface=primary {hostname=<%= realhostname %>
- ip_address=<%= ip %>
- netmask=<%= netmask %>
+ name_service=DNS {domain_name=<%= domain %> name_server=<%= nameserver %>}
+ network_interface=primary {hostname=<%= realhostname %>
+ ip_address=<%= ip %>
+ netmask=<%= netmask %>
protocol_ipv6=no
- default_route=<%= defaultroute %>}
+ default_route=<%= defaultroute %>}
nfs4_domain=dynamic
And then call that:
zone { myzone:
ip => "bge0:192.168.0.23",
sysidcfg => template(sysidcfg),
path => "/opt/zones/myzone",
realhostname => "fully.qualified.domain.name"
}
The sysidcfg only matters on the first booting of the zone,
so Puppet only checks for it at that time.}
end
newparam(:path) do
desc "The root of the zone's filesystem. Must be a fully qualified
file name. If you include '%s' in the path, then it will be
replaced with the zone's name. At this point, you cannot use
Puppet to move a zone."
validate do |value|
unless value =~ /^\//
raise ArgumentError, "The zone base must be fully qualified"
end
end
munge do |value|
if value =~ /%s/
value % @resource[:name]
else
value
end
end
end
newparam(:create_args) do
desc "Arguments to the zonecfg create command. This can be used to create branded zones."
end
newparam(:install_args) do
desc "Arguments to the zoneadm install command. This can be used to create branded zones."
end
newparam(:realhostname) do
desc "The actual hostname of the zone."
end
# If Puppet is also managing the base dir or its parent dir, list them
# both as prerequisites.
autorequire(:file) do
if @parameters.include? :path
[@parameters[:path].value, File.dirname(@parameters[:path].value)]
else
nil
end
end
def validate_ip(ip, name)
IPAddr.new(ip) if ip
rescue ArgumentError
self.fail "'#{ip}' is an invalid #{name}"
end
validate do
value = self[:ip]
interface, address, defrouter = value.split(':')
if self[:iptype] == :shared
if (interface && address && defrouter.nil?) ||
(interface && address && defrouter)
validate_ip(address, "IP address")
validate_ip(defrouter, "default router")
else
self.fail "ip must contain interface name and ip address separated by a \":\""
end
else
self.fail "only interface may be specified when using exclusive IP stack: #{value}" unless interface && address.nil? && defrouter.nil?
end
self.fail "zone path is required" unless self[:path]
end
def retrieve
provider.flush
if hash = provider.properties and hash[:ensure] != :absent
result = setstatus(hash)
result
else
# Return all properties as absent.
return properties.inject({}) do | prophash, property|
prophash[property] = :absent
prophash
end
end
end
# Take the results of a listing and set everything appropriately.
def setstatus(hash)
prophash = {}
hash.each do |param, value|
next if param == :name
case self.class.attrtype(param)
when :property
# Only try to provide values for the properties we're managing
if prop = self.property(param)
prophash[prop] = value
end
else
self[param] = value
end
end
prophash
end
end
diff --git a/lib/puppet/type/zpool.rb b/lib/puppet/type/zpool.rb
index 401a2c220..49cce552a 100755
--- a/lib/puppet/type/zpool.rb
+++ b/lib/puppet/type/zpool.rb
@@ -1,92 +1,92 @@
module Puppet
class Property
class VDev < Property
def flatten_and_sort(array)
array.collect { |a| a.split(' ') }.flatten.sort
end
def insync?(is)
return true unless self.should
return @should == [:absent] if is == :absent
flatten_and_sort(is) == flatten_and_sort(@should)
end
end
class MultiVDev < VDev
def insync?(is)
return true unless self.should
return @should == [:absent] if is == :absent
return false unless is.length == @should.length
is.each_with_index { |list, i| return false unless flatten_and_sort(list) == flatten_and_sort(@should[i]) }
#if we made it this far we are in sync
true
end
end
end
newtype(:zpool) do
@doc = "Manage zpools. Create and delete zpools. The provider WILL NOT SYNC, only report differences.
Supports vdevs with mirrors, raidz, logs and spares."
ensurable
newproperty(:disk, :array_matching => :all, :parent => Puppet::Property::VDev) do
desc "The disk(s) for this pool. Can be an array or space separated string"
end
newproperty(:mirror, :array_matching => :all, :parent => Puppet::Property::MultiVDev) do
desc "List of all the devices to mirror for this pool. Each mirror should be a space separated string:
-
+
mirror => [\"disk1 disk2\", \"disk3 disk4\"]
-
+
"
validate do |value|
raise ArgumentError, "mirror names must be provided as string separated, not a comma-separated list" if value.include?(",")
end
end
newproperty(:raidz, :array_matching => :all, :parent => Puppet::Property::MultiVDev) do
desc "List of all the devices to raid for this pool. Should be an array of space separated strings:
-
- raidz => [\"disk1 disk2\", \"disk3 disk4\"]
+
+ raidz => [\"disk1 disk2\", \"disk3 disk4\"]
"
validate do |value|
raise ArgumentError, "raid names must be provided as string separated, not a comma-separated list" if value.include?(",")
end
end
newproperty(:spare, :array_matching => :all, :parent => Puppet::Property::VDev) do
desc "Spare disk(s) for this pool."
end
newproperty(:log, :array_matching => :all, :parent => Puppet::Property::VDev) do
desc "Log disks for this pool. (doesn't support mirroring yet)"
end
newparam(:pool) do
desc "The name for this pool."
isnamevar
end
newparam(:raid_parity) do
desc "Determines parity when using raidz property."
end
validate do
has_should = [:disk, :mirror, :raidz].select { |prop| self.should(prop) }
self.fail "You cannot specify #{has_should.join(" and ")} on this type (only one)" if has_should.length > 1
end
end
end
diff --git a/lib/puppet/util/command_line/puppetdoc b/lib/puppet/util/command_line/puppetdoc
index d9bbbec33..0fa1830d6 100755
--- a/lib/puppet/util/command_line/puppetdoc
+++ b/lib/puppet/util/command_line/puppetdoc
@@ -1,67 +1,67 @@
#!/usr/bin/env ruby
#
# = Synopsis
#
# Generate a reference for all Puppet types. Largely meant for internal Reductive
# Labs use.
#
# = Usage
#
-# puppet doc [-a|--all] [-h|--help] [-o|--outputdir ] [-m|--mode ]
+# puppet doc [-a|--all] [-h|--help] [-o|--outputdir ] [-m|--mode ]
# [-r|--reference <[type]|configuration|..>] [--charset CHARSET] [manifest-file]
#
# = Description
#
-# If mode is not 'rdoc', then this command generates a restructured-text document describing all installed
+# If mode is not 'rdoc', then this command generates a Markdown document describing all installed
# Puppet types or all allowable arguments to puppet executables. It is largely
# meant for internal use and is used to generate the reference document
-# available on the Reductive Labs web site.
+# available on the Puppet Labs web site.
#
# In 'rdoc' mode, this command generates an html RDoc hierarchy describing the manifests that
# are in 'manifestdir' and 'modulepath' configuration directives.
# The generated documentation directory is doc by default but can be changed with the 'outputdir' option.
#
# If the command is started with 'manifest-file' command-line arguments, puppet doc generate a single
# manifest documentation that is output on stdout.
#
# = Options
#
# all::
# Output the docs for all of the reference types. In 'rdoc' modes, this also outputs documentation for all resources
#
# help::
# Print this help message
#
# outputdir::
# Specifies the directory where to output the rdoc documentation in 'rdoc' mode.
#
# mode::
-# Determine the output mode. Valid modes are 'text', 'trac', 'pdf', 'markdown' and 'rdoc'. The 'pdf' and 'markdown' modes create PDF or Markdown formatted files in the /tmp directory. Note that 'trac' mode only works on Reductive Labs servers. The default mode is 'text'. In 'rdoc' mode you must provide 'manifests-path'
+# Determine the output mode. Valid modes are 'text', 'trac', 'pdf' and 'rdoc'. The 'pdf' mode creates PDF formatted files in the /tmp directory. The default mode is 'text'. In 'rdoc' mode you must provide 'manifests-path'
#
# reference::
# Build a particular reference. Get a list of references by running +puppet doc --list+.
#
# charset::
# Used only in 'rdoc' mode. It sets the charset used in the html files produced.
#
# = Example
#
# $ puppet doc -r type > /tmp/type_reference.rst
# or
# $ puppet doc --outputdir /tmp/rdoc --mode rdoc /path/to/manifests
# or
# $ puppet doc /etc/puppet/manifests/site.pp
# or
-# $ puppet doc -m markdown -r configuration
+# $ puppet doc -m pdf -r configuration
#
# = Author
#
# Luke Kanies
#
# = Copyright
#
# Copyright (c) 2005-2007 Reductive Labs, LLC
# Licensed under the GNU Public License
#Puppet::Application[:doc].run
diff --git a/lib/puppet/util/command_line/ralsh b/lib/puppet/util/command_line/ralsh
index 68ad92d84..83338fcbc 100755
--- a/lib/puppet/util/command_line/ralsh
+++ b/lib/puppet/util/command_line/ralsh
@@ -1,89 +1,89 @@
#!/usr/bin/env ruby
#
# = Synopsis
#
# Use the Puppet RAL to directly interact with the system.
#
# = Usage
#
# puppet resource [-h|--help] [-d|--debug] [-v|--verbose] [-e|--edit]
# [-H|--host ] [-p|--param ] [-t|--types]
# type
#
# = Description
#
# This command provides simple facilities for converting current system state
# into Puppet code, along with some ability to use Puppet to affect the current
# state.
#
# By default, you must at least provide a type to list, which case puppet resource
# will tell you everything it knows about all instances of that type. You can
# optionally specify an instance name, and puppet resource will only describe that single
# instance.
#
# You can also add +--edit+ as an argument, and puppet resource will write its output
# to a file, open that file in an editor, and then apply the file as a Puppet
# transaction. You can easily use this to use Puppet to make simple changes to
# a system.
#
# = Options
#
# Note that any configuration parameter that's valid in the configuration file
# is also a valid long argument. For example, 'ssldir' is a valid configuration
# parameter, so you can specify '--ssldir ' as an argument.
#
# See the configuration file documentation at
# http://docs.puppetlabs.com/references/stable/configuration.html for
# the full list of acceptable parameters. A commented list of all
# configuration options can also be generated by running puppet with
# '--genconfig'.
#
# debug::
# Enable full debugging.
#
# edit:
# Write the results of the query to a file, open the file in an editor,
# and read the file back in as an executable Puppet manifest.
#
# host:
# When specified, connect to the resource server on the named host
# and retrieve the list of resouces of the type specified.
#
# help:
# Print this help message.
#
# param:
# Add more parameters to be outputted from queries.
#
# types:
# List all available types.
#
-# verbose::
+# verbose:
# Print extra information.
#
# = Example
#
-# This example uses ``puppet resource`` to return Puppet configuration for the user ``luke``::
-#
-# $ puppet resource user luke
-# user { 'luke':
-# home => '/home/luke',
-# uid => '100',
-# ensure => 'present',
-# comment => 'Luke Kanies,,,',
-# gid => '1000',
-# shell => '/bin/bash',
-# groups => ['sysadmin','audio','video','puppet']
-# }
+# This example uses `puppet resource` to return Puppet configuration for the user `luke`:
+#
+# $ puppet resource user luke
+# user { 'luke':
+# home => '/home/luke',
+# uid => '100',
+# ensure => 'present',
+# comment => 'Luke Kanies,,,',
+# gid => '1000',
+# shell => '/bin/bash',
+# groups => ['sysadmin','audio','video','puppet']
+# }
#
# = Author
#
# Luke Kanies
#
# = Copyright
#
# Copyright (c) 2005-2007 Reductive Labs, LLC
# Licensed under the GNU Public License
#Puppet::Application[:resource].run
diff --git a/lib/puppet/util/docs.rb b/lib/puppet/util/docs.rb
index 1746ef3fc..4344d67ab 100644
--- a/lib/puppet/util/docs.rb
+++ b/lib/puppet/util/docs.rb
@@ -1,108 +1,107 @@
# Some simple methods for helping manage automatic documentation generation.
module Puppet::Util::Docs
# Specify the actual doc string.
def desc(str)
@doc = str
end
# Add a new autodoc block. We have to define these as class methods,
# rather than just sticking them in a hash, because otherwise they're
# too difficult to do inheritance with.
def dochook(name, &block)
method = "dochook_#{name}"
meta_def method, &block
end
attr_writer :doc
# Generate the full doc string.
def doc
extra = methods.find_all { |m| m.to_s =~ /^dochook_.+/ }.sort.collect { |m|
self.send(m)
}.join(" ")
if @doc
@doc + extra
else
extra
end
end
# Build a table
def doctable(headers, data)
str = "\n\n"
lengths = []
# Figure out the longest field for all columns
data.each do |name, values|
[name, values].flatten.each_with_index do |value, i|
lengths[i] ||= 0
lengths[i] = value.to_s.length if value.to_s.length > lengths[i]
end
end
# The headers could also be longest
headers.each_with_index do |value, i|
lengths[i] = value.to_s.length if value.to_s.length > lengths[i]
end
# Add the header names
str += headers.zip(lengths).collect { |value, num| pad(value, num) }.join(" | ") + " |" + "\n"
# And the header row
str += lengths.collect { |num| "-" * num }.join(" | ") + " |" + "\n"
# Now each data row
data.sort { |a, b| a[0].to_s <=> b[0].to_s }.each do |name, rows|
str += [name, rows].flatten.zip(lengths).collect do |value, length|
pad(value, length)
end.join(" | ") + " |" + "\n"
end
str + "\n"
end
attr_reader :nodoc
def nodoc?
nodoc
end
# Pad a field with spaces
def pad(value, length)
value.to_s + (" " * (length - value.to_s.length))
end
# Handle the inline indentation in the docs.
def scrub(text)
# Stupid markdown
#text = text.gsub("<%=", "<%=")
# For text with no carriage returns, there's nothing to do.
return text if text !~ /\n/
indent = nil
# If we can match an indentation, then just remove that same level of
# indent from every line. However, ignore any indentation on the
# first line, since that can be inconsistent.
text = text.lstrip
text.gsub!(/^([\t]+)/) { |s| " "*8*s.length; } # Expand leading tabs
# Find first non-empty line after the first line:
line2start = (text =~ /(\n?\s*\n)/)
line2start += $1.length
if (text[line2start..-1] =~ /^([ ]+)\S/) == 0
indent = Regexp.quote($1)
begin
return text.gsub(/^#{indent}/,'')
rescue => detail
puts detail.backtrace
puts detail
end
else
return text
end
end
module_function :scrub
end
-
diff --git a/lib/puppet/util/reference.rb b/lib/puppet/util/reference.rb
index 00390746e..4f2058e69 100644
--- a/lib/puppet/util/reference.rb
+++ b/lib/puppet/util/reference.rb
@@ -1,184 +1,158 @@
require 'puppet/util/instance_loader'
require 'fileutils'
# Manage Reference Documentation.
class Puppet::Util::Reference
include Puppet::Util
include Puppet::Util::Docs
extend Puppet::Util::InstanceLoader
instance_load(:reference, 'puppet/reference')
def self.footer
"\n\n----------------\n\n*This page autogenerated on #{Time.now}*\n"
end
def self.modes
- %w{pdf text markdown}
+ %w{pdf text}
end
def self.newreference(name, options = {}, &block)
ref = self.new(name, options, &block)
instance_hash(:reference)[symbolize(name)] = ref
ref
end
def self.page(*sections)
depth = 4
# Use the minimum depth
sections.each do |name|
section = reference(name) or raise "Could not find section #{name}"
depth = section.depth if section.depth < depth
end
text = "{:toc}\n\n"
end
def self.pdf(text)
puts "creating pdf"
Puppet::Util.secure_open("/tmp/puppetdoc.txt", "w") do |f|
f.puts text
end
rst2latex = %x{which rst2latex}
if $CHILD_STATUS != 0 or rst2latex =~ /no /
rst2latex = %x{which rst2latex.py}
end
if $CHILD_STATUS != 0 or rst2latex =~ /no /
raise "Could not find rst2latex"
end
rst2latex.chomp!
cmd = %{#{rst2latex} /tmp/puppetdoc.txt > /tmp/puppetdoc.tex}
Puppet::Util.secure_open("/tmp/puppetdoc.tex","w") do |f|
# If we get here without an error, /tmp/puppetdoc.tex isn't a tricky cracker's symlink
end
output = %x{#{cmd}}
unless $CHILD_STATUS == 0
$stderr.puts "rst2latex failed"
$stderr.puts output
exit(1)
end
$stderr.puts output
# Now convert to pdf
Dir.chdir("/tmp") do
%x{texi2pdf puppetdoc.tex >/dev/null 2>/dev/null}
end
end
- def self.markdown(name, text)
- puts "Creating markdown for #{name} reference."
- dir = "/tmp/#{Puppet::PUPPETVERSION}"
- FileUtils.mkdir(dir) unless File.directory?(dir)
- Puppet::Util.secure_open(dir + "/#{name}.rst", "w") do |f|
- f.puts text
- end
- pandoc = %x{which pandoc}
- if $CHILD_STATUS != 0 or pandoc =~ /no /
- pandoc = %x{which pandoc}
- end
- if $CHILD_STATUS != 0 or pandoc =~ /no /
- raise "Could not find pandoc"
- end
- pandoc.chomp!
- cmd = %{#{pandoc} -s -r rst -w markdown #{dir}/#{name}.rst -o #{dir}/#{name}.mdwn}
- output = %x{#{cmd}}
- unless $CHILD_STATUS == 0
- $stderr.puts "Pandoc failed to create #{name} reference."
- $stderr.puts output
- exit(1)
- end
-
- File.unlink(dir + "/#{name}.rst")
- end
-
def self.references
instance_loader(:reference).loadall
loaded_instances(:reference).sort { |a,b| a.to_s <=> b.to_s }
end
HEADER_LEVELS = [nil, "=", "-", "+", "'", "~"]
attr_accessor :page, :depth, :header, :title, :dynamic
attr_writer :doc
def doc
if defined?(@doc)
return "#{@name} - #{@doc}"
else
return @title
end
end
def dynamic?
self.dynamic
end
def h(name, level)
"#{name}\n#{HEADER_LEVELS[level] * name.to_s.length}\n\n"
end
def initialize(name, options = {}, &block)
@name = name
options.each do |option, value|
send(option.to_s + "=", value)
end
meta_def(:generate, &block)
# Now handle the defaults
@title ||= "#{@name.to_s.capitalize} Reference"
@page ||= @title.gsub(/\s+/, '')
@depth ||= 2
@header ||= ""
end
# Indent every line in the chunk except those which begin with '..'.
def indent(text, tab)
text.gsub(/(^|\A)/, tab).gsub(/^ +\.\./, "..")
end
def option(name, value)
":#{name.to_s.capitalize}: #{value}\n"
end
def paramwrap(name, text, options = {})
options[:level] ||= 5
#str = "#{name} : "
str = h(name, options[:level])
str += "- **namevar**\n\n" if options[:namevar]
str += text
#str += text.gsub(/\n/, "\n ")
str += "\n\n"
end
# Remove all trac links.
def strip_trac(text)
text.gsub(/`\w+\s+([^`]+)`:trac:/) { |m| $1 }
end
def text
puts output
end
def to_rest(withcontents = true)
# First the header
text = h(@title, 1)
text += "\n\n**This page is autogenerated; any changes will get overwritten** *(last generated on #{Time.now.to_s})*\n\n"
text += "{:toc}\n\n" if withcontents
text += @header
text += generate
text += self.class.footer if withcontents
text
end
def to_text(withcontents = true)
strip_trac(to_rest(withcontents))
end
end
diff --git a/spec/unit/parameter/value_collection_spec.rb b/spec/unit/parameter/value_collection_spec.rb
index 78c2c5263..cb82d1517 100755
--- a/spec/unit/parameter/value_collection_spec.rb
+++ b/spec/unit/parameter/value_collection_spec.rb
@@ -1,167 +1,167 @@
#!/usr/bin/env ruby
require File.dirname(__FILE__) + '/../../spec_helper'
require 'puppet/parameter'
describe Puppet::Parameter::ValueCollection do
before do
@collection = Puppet::Parameter::ValueCollection.new
end
it "should have a method for defining new values" do
@collection.should respond_to(:newvalues)
end
it "should have a method for adding individual values" do
@collection.should respond_to(:newvalue)
end
it "should be able to retrieve individual values" do
value = @collection.newvalue(:foo)
@collection.value(:foo).should equal(value)
end
it "should be able to add an individual value with a block" do
@collection.newvalue(:foo) { raise "testing" }
@collection.value(:foo).block.should be_instance_of(Proc)
end
it "should be able to add values that are empty strings" do
lambda { @collection.newvalue('') }.should_not raise_error
end
it "should be able to add values that are empty strings" do
value = @collection.newvalue('')
@collection.match?('').should equal(value)
end
it "should set :call to :none when adding a value with no block" do
value = @collection.newvalue(:foo)
value.call.should == :none
end
describe "when adding a value with a block" do
it "should set the method name to 'set_' plus the value name" do
value = @collection.newvalue(:myval) { raise "testing" }
value.method.should == "set_myval"
end
end
it "should be able to add an individual value with options" do
value = @collection.newvalue(:foo, :call => :bar)
value.call.should == :bar
end
it "should have a method for validating a value" do
@collection.should respond_to(:validate)
end
it "should have a method for munging a value" do
@collection.should respond_to(:munge)
end
it "should be able to generate documentation when it has both values and regexes" do
@collection.newvalues :foo, "bar", %r{test}
@collection.doc.should be_instance_of(String)
end
it "should correctly generate documentation for values" do
@collection.newvalues :foo
- @collection.doc.should be_include("Valid values are ``foo``")
+ @collection.doc.should be_include("Valid values are `foo`")
end
it "should correctly generate documentation for regexes" do
@collection.newvalues %r{\w+}
- @collection.doc.should be_include("Values can match ``/\\w+/``")
+ @collection.doc.should be_include("Values can match `/\\w+/`")
end
it "should be able to find the first matching value" do
@collection.newvalues :foo, :bar
@collection.match?("foo").should be_instance_of(Puppet::Parameter::Value)
end
it "should be able to match symbols" do
@collection.newvalues :foo, :bar
@collection.match?(:foo).should be_instance_of(Puppet::Parameter::Value)
end
it "should be able to match symbols when a regex is provided" do
@collection.newvalues %r{.}
@collection.match?(:foo).should be_instance_of(Puppet::Parameter::Value)
end
it "should be able to match values using regexes" do
@collection.newvalues %r{.}
@collection.match?("foo").should_not be_nil
end
it "should prefer value matches to regex matches" do
@collection.newvalues %r{.}, :foo
@collection.match?("foo").name.should == :foo
end
describe "when validating values" do
it "should do nothing if no values or regexes have been defined" do
@collection.validate("foo")
end
it "should fail if the value is not a defined value or alias and does not match a regex" do
@collection.newvalues :foo
lambda { @collection.validate("bar") }.should raise_error(ArgumentError)
end
it "should succeed if the value is one of the defined values" do
@collection.newvalues :foo
lambda { @collection.validate(:foo) }.should_not raise_error(ArgumentError)
end
it "should succeed if the value is one of the defined values even if the definition uses a symbol and the validation uses a string" do
@collection.newvalues :foo
lambda { @collection.validate("foo") }.should_not raise_error(ArgumentError)
end
it "should succeed if the value is one of the defined values even if the definition uses a string and the validation uses a symbol" do
@collection.newvalues "foo"
lambda { @collection.validate(:foo) }.should_not raise_error(ArgumentError)
end
it "should succeed if the value is one of the defined aliases" do
@collection.newvalues :foo
@collection.aliasvalue :bar, :foo
lambda { @collection.validate("bar") }.should_not raise_error(ArgumentError)
end
it "should succeed if the value matches one of the regexes" do
@collection.newvalues %r{\d}
lambda { @collection.validate("10") }.should_not raise_error(ArgumentError)
end
end
describe "when munging values" do
it "should do nothing if no values or regexes have been defined" do
@collection.munge("foo").should == "foo"
end
it "should return return any matching defined values" do
@collection.newvalues :foo, :bar
@collection.munge("foo").should == :foo
end
it "should return any matching aliases" do
@collection.newvalues :foo
@collection.aliasvalue :bar, :foo
@collection.munge("bar").should == :foo
end
it "should return the value if it matches a regex" do
@collection.newvalues %r{\w}
@collection.munge("bar").should == "bar"
end
it "should return the value if no other option is matched" do
@collection.newvalues :foo
@collection.munge("bar").should == "bar"
end
end
end