diff --git a/bin/puppetdoc b/bin/puppetdoc
index 3b3d2babb..8185ed773 100755
--- a/bin/puppetdoc
+++ b/bin/puppetdoc
@@ -1,341 +1,346 @@
#!/usr/bin/env ruby
#
# = Synopsis
#
# Generate a reference for all Puppet types. Largely meant for internal Reductive
# Labs use.
#
# = Usage
#
# puppetdoc [-h|--help] [-a|--arguments] [-t|--types]
#
# = Description
#
# This command generates a restructured-text 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.
#
# = Options
#
# arguments::
# Print the documentation for arguments.
#
# help::
# Print this help message
#
# types::
# Print the argumenst for Puppet types. This is the default.
#
# = Example
#
# $ puppetdoc > /tmp/reference.rst
#
# = Author
#
# Luke Kanies
#
# = Copyright
#
# Copyright (c) 2005 Reductive Labs, LLC
# Licensed under the GNU Public License
require 'puppet'
require 'getoptlong'
$haveusage = true
begin
require 'rdoc/usage'
rescue Exception
$haveusage = false
end
result = GetoptLong.new(
[ "--arguments", "-a", GetoptLong::NO_ARGUMENT ],
[ "--types", "-t", GetoptLong::NO_ARGUMENT ],
[ "--help", "-h", GetoptLong::NO_ARGUMENT ]
)
debug = false
$tab = " "
mode = :types
begin
result.each { |opt,arg|
case opt
when "--arguments"
mode = :arguments
when "--types"
mode = :types
when "--help"
if $haveusage
RDoc::usage && exit
else
puts "No help available unless you have RDoc::usage installed"
exit
end
end
}
rescue GetoptLong::InvalidOption => detail
$stderr.puts "Try '#{$0} --help'"
#if $haveusage
# RDoc::usage_no_exit('usage')
#end
exit(1)
end
def scrub(text)
+
+ # Stupid markdown
+ #text = text.gsub("<%=", "<%=")
# For text with no carriage returns, there's nothing to do.
if text !~ /\n/
return text
end
indent = nil
# If we can match an indentation, then just remove that same level of
# indent from every line.
if text =~ /^(\s+)/
indent = $1
begin
return text.gsub(/^#{indent}/,'')
rescue => detail
puts detail.backtrace
puts detail
end
else
return text
end
end
# Indent every line in the chunk except those which begin with '..'.
def indent(text, tab)
return text.gsub(/(^|\A)/, tab).gsub(/^ +\.\./, "..")
end
def paramwrap(name, text, namevar = false)
if namevar
name = name.to_s + " (*namevar*)"
end
puts "#### %s" % name
puts text
puts ""
end
# Print the docs for arguments
def self.arguments
puts %{---
inMenu: true
title: Configuration Reference
orderInfo: 6
---
# Puppet Configuration Reference
Every Puppet executable (with the exception of ``puppetdoc``) accepts all of
these arguments, but not all of the arguments make sense for every executable.
Each argument has a section listed with it in parentheses; often, that section
will map to an executable (e.g., ``puppetd``), in which case it probably only
makes sense for that one executable. If ``puppet`` is listed as the section,
it is most likely an option that is valid for everyone.
This will not always be the case. I have tried to be as thorough as possible
in the descriptions of the arguments, so it should be obvious whether an
argument is approprite or not.
Any default values are in ``block type`` at the end of the description.
}
docs = {}
Puppet.config.each do |name, object|
docs[name] = object
end
docs.sort { |a, b|
a[0].to_s <=> b[0].to_s
}.each do |name, object|
# Make each name an anchor
puts %{#### #{name.to_s} (#{object.section.to_s})}
puts ""
default = ""
if val = object.value and val != ""
default = " ``%s``" % val
end
begin
puts object.desc.gsub(/\n/, " ") + default
rescue => detail
puts detail.backtrace
puts detail
end
puts ""
end
end
# Print the docs for types
def self.types
puts %{---
inMenu: true
title: Type Reference
orderInfo: 4
---
# Type Reference
}
types = {}
+ Puppet::Type.loadall
+
Puppet::Type.eachtype { |type|
next if type.name == :puppet
next if type.name == :component
types[type.name] = type
}
# Build a simple TOC
puts "## Table of Contents"
puts "1. Meta-Parameters"
types.sort { |a, b| a[0].to_s <=> b[0].to_s }.each do |name, type|
puts "1. %s" % [type.name, type.name.to_s.capitalize]
end
puts %{
Metaparameters are parameters that work with any element; they are part of the
Puppet framework itself rather than being part of the implementation of any
given instance. Thus, any defined metaparameter can be used with any instance
in your manifest, including defined components.
}
begin
params = []
Puppet::Type.eachmetaparam { |param|
params << param
}
params.sort { |a,b|
a.to_s <=> b.to_s
}.each { |param|
paramwrap(param.to_s, scrub(Puppet::Type.metaparamdoc(param)))
#puts "" + param.to_s + ""
#puts tab(1) + Puppet::Type.metaparamdoc(param).scrub.indent($tab)gsub(/\n\s*/,' ')
#puts ""
#puts indent(scrub(Puppet::Type.metaparamdoc(param)), $tab)
#puts scrub(Puppet::Type.metaparamdoc(param))
#puts ""
#puts ""
}
rescue => detail
puts detail.backtrace
puts "incorrect metaparams: %s" % detail
exit(1)
end
puts %{
## Types
- *namevar* is the parameter used to uniquely identify a type instance.
This is the parameter that gets assigned when a string is provided before
the colon in a type declaration. In general, only developers will need to
worry about which parameter is the ``namevar``.
In the following code:
file { "/etc/passwd":
owner => root,
group => root,
mode => 644
}
"/etc/passwd" is considered the name of the file object (used for things like
dependency handling), and because ``path`` is the namevar for ``file``, that
string is assigned to the ``path`` parameter.
- *parameters* determine the specific configuration of the instance. They either
directly modify the system (internally, these are called states) or they affect
how the instance behaves (e.g., adding a search path for ``exec`` instances
or determining recursion on ``file`` instances).
When required binaries are specified for providers, fully qualifed paths
indicate that the binary must exist at that specific path and unqualified
binaries indicate that Puppet will search for the binary using the shell
path.
}
types.sort { |a,b|
a.to_s <=> b.to_s
}.each { |name,type|
puts "
----------------
"
puts "
" % [name, name]
puts scrub(type.doc) + "\n\n"
docs = {}
type.validstates.sort { |a,b|
a.to_s <=> b.to_s
}.reject { |sname|
state = type.statebyname(sname)
state.nodoc
}.each { |sname|
state = type.statebyname(sname)
unless state
raise "Could not retrieve state %s on type %s" % [sname, type.name]
end
doc = nil
str = nil
unless doc = state.doc
$stderr.puts "No docs for %s[%s]" % [type, sname]
next
end
doc = doc.dup
str = doc
str = scrub(str)
#str = indent(str, $tab)
docs[sname] = str
}
puts "\n### %s Parameters\n" % name.to_s.capitalize
type.parameters.sort { |a,b|
a.to_s <=> b.to_s
}.each { |name,param|
#docs[name] = indent(scrub(type.paramdoc(name)), $tab)
docs[name] = scrub(type.paramdoc(name))
}
docs.sort { |a, b|
a[0].to_s <=> b[0].to_s
}.each { |name, doc|
namevar = type.namevar == name and name != :name
paramwrap(name, doc, namevar)
}
puts "\n"
}
end
send(mode)
puts "
----------------
"
puts "\n*This page autogenerated on %s*" % Time.now
# $Id$
diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb
index 0e0628202..13caf1973 100644
--- a/lib/puppet/type.rb
+++ b/lib/puppet/type.rb
@@ -1,2663 +1,2677 @@
require 'puppet'
require 'puppet/log'
require 'puppet/element'
require 'puppet/event'
require 'puppet/metric'
require 'puppet/type/state'
require 'puppet/parameter'
require 'puppet/util'
require 'puppet/autoload'
# see the bottom of the file for the rest of the inclusions
module Puppet
# The type is unknown
class UnknownTypeError < Puppet::Error; end
class UnknownProviderError < Puppet::Error; end
class Type < Puppet::Element
# Types (which map to elements 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 states.
# 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_accessor :children
attr_reader :provider
attr_accessor :file, :line
attr_reader :tags, :parent
attr_writer :implicit, :title
def implicit?
if defined? @implicit and @implicit
return true
else
return false
end
end
include Enumerable
# class methods dealing with Type management
public
# the Type class attribute accessors
class << self
attr_reader :name, :states
attr_accessor :providerloader
attr_writer :defaultprovider
include Enumerable, Puppet::Util::ClassGen
end
# iterate across all of the subclasses of Type
def self.eachtype
@types.each do |name, type|
# Only consider types that have names
#if ! type.parameters.empty? or ! type.validstates.empty?
yield type
#end
end
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.newstate(:ensure, :parent => Puppet::State::Ensure, &block)
else
self.newstate(:ensure, :parent => Puppet::State::Ensure) do
self.defaultvalues
end
end
end
# Should we add the 'ensure' state to this class?
def self.ensurable?
# If the class has all three of these methods defined, then it's
# ensurable.
#ens = [:create, :destroy].inject { |set, method|
ens = [:exists?, :create, :destroy].inject { |set, method|
set &&= self.public_method_defined?(method)
}
#puts "%s ensurability: %s" % [self.name, ens]
return ens
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
@providers = Hash.new
@defaults = {}
unless defined? @parameters
@parameters = []
end
@validstates = {}
@states = []
@parameters = []
@paramhash = {}
@paramdoc = Hash.new { |hash,key|
if key.is_a?(String)
key = key.intern
end
if hash.include?(key)
hash[key]
else
"Param Documentation for %s not found" % key
end
}
unless defined? @doc
@doc = ""
end
unless defined? @states
@states = []
end
end
+ # Load all types. Only currently used for documentation.
+ def self.loadall
+ typeloader.loadall
+ end
+
# Do an on-demand plugin load
def self.loadplugin(name)
unless Puppet[:pluginpath].split(":").include?(Puppet[:plugindest])
Puppet.notice "Adding plugin destination %s to plugin search path" %
Puppet[:plugindest]
Puppet[:pluginpath] += ":" + Puppet[:plugindest]
end
Puppet[:pluginpath].split(":").each do |dir|
file = ::File.join(dir, name.to_s + ".rb")
if FileTest.exists?(file)
begin
load file
Puppet.info "loaded %s" % file
return true
rescue LoadError => detail
Puppet.info "Could not load plugin %s: %s" %
[file, detail]
return false
end
end
end
end
# Define a new type.
def self.newtype(name, parent = nil, &block)
# First make sure we don't have a method sitting around
name = symbolize(name)
newmethod = "new#{name.to_s}"
# Used for method manipulation.
selfobj = metaclass()
@types ||= {}
if @types.include?(name)
if self.respond_to?(newmethod)
# Remove the old newmethod
selfobj.send(:remove_method,newmethod)
end
end
# Then create the class.
klass = genclass(name,
:parent => (parent || Puppet::Type),
:overwrite => true,
:hash => @types,
&block
)
# Now define a "new" method for convenience.
if self.respond_to? newmethod
# Refuse to overwrite existing methods like 'newparam' or 'newtype'.
Puppet.warning "'new#{name.to_s}' method already exists; skipping"
else
selfobj.send(:define_method, newmethod) do |*args|
klass.create(*args)
end
end
# If they've got all the necessary methods defined and they haven't
# already added the state, then do so now.
if klass.ensurable? and ! klass.validstate?(:ensure)
klass.ensurable
end
# Now set up autoload any providers that might exist for this type.
klass.providerloader = Puppet::Autoload.new(klass,
"puppet/provider/#{klass.name.to_s}"
)
# We have to load everything so that we can figure out the default type.
klass.providerloader.loadall()
klass
end
# Return a Type instance by name.
def self.type(name)
@types ||= {}
if name.is_a?(String)
name = name.intern
end
unless @types.include? name
- begin
- require "puppet/type/#{name}"
+ if typeloader.load(name)
unless @types.include? name
Puppet.warning "Loaded puppet/type/#{name} but no class was created"
end
- rescue LoadError => detail
+ else
# If we can't load it from there, try loading it as a plugin.
loadplugin(name)
end
end
@types[name]
end
+ def self.typeloader
+ unless defined? @typeloader
+ @typeloader = Puppet::Autoload.new(self,
+ "puppet/type", :wrap => false
+ )
+ end
+
+ @typeloader
+ end
+
# class methods dealing with type instance management
public
# 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)
if @objects.include?(name)
unless @objects[name] == obj
raise Puppet::Error.new(
"Cannot create alias %s: object already exists" %
[name]
)
end
end
if @aliases.include?(name)
unless @aliases[name] == obj
raise Puppet::Error.new(
"Object %s already has alias %s" %
[@aliases[name].name, name]
)
end
end
@aliases[name] = obj
end
# retrieve a named instance of the current type
def self.[](name)
if @objects.has_key?(name)
return @objects[name]
elsif @aliases.has_key?(name)
return @aliases[name]
else
return nil
end
end
# add an instance by name to the class list of instances
def self.[]=(name,object)
newobj = nil
if object.is_a?(Puppet::Type)
newobj = object
else
raise Puppet::DevError, "must pass a Puppet::Type object"
end
if exobj = @objects.has_key?(name) and self.isomorphic?
msg = "Object '%s[%s]' already exists" %
[name, newobj.class.name]
if exobj.file and exobj.line
msg += ("in file %s at line %s" %
[object.file, object.line])
end
if object.file and object.line
msg += ("and cannot be redefined in file %s at line %s" %
[object.file, object.line])
end
error = Puppet::Error.new(msg)
else
#Puppet.info("adding %s of type %s to class list" %
# [name,object.class])
@objects[name] = newobj
end
end
# remove all type instances; this is mostly only useful for testing
def self.allclear
Puppet::Event::Subscription.clear
@types.each { |name, type|
type.clear
}
end
# remove all of the instances of a single type
def self.clear
if defined? @objects
@objects.each do |name, obj|
obj.remove(true)
end
@objects.clear
end
if defined? @aliases
@aliases.clear
end
end
# remove a specified object
def self.delete(object)
return unless defined? @objects
if @objects.include?(object.title)
@objects.delete(object.title)
end
if @aliases.include?(object.title)
@aliases.delete(object.title)
end
end
# iterate across each of the type's instances
def self.each
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)
return @objects.has_key?(name)
end
# Allow an outside party to specify the 'is' value for a state. The
# arguments are an array because you can't use parens with 'is=' calls.
# Most classes won't use this.
def is=(ary)
param, value = ary
if param.is_a?(String)
param = param.intern
end
if self.class.validstate?(param)
unless @states.include?(param)
self.newstate(param)
end
@states[param].is = value
else
self[param] = value
end
end
# class and instance methods dealing with parameters and states
public
# Find the namevar
def self.namevar
unless defined? @namevar
params = @parameters.find_all { |param|
param.isnamevar? or param.name == :name
}
if params.length > 1
raise Puppet::DevError, "Found multiple namevars for %s" % self.name
elsif params.length == 1
@namevar = params[0].name
else
raise Puppet::DevError, "No namevar for %s" % self.name
end
end
@namevar
end
# Copy an existing class parameter. This allows other types to avoid
# duplicating a parameter definition, and is mostly used by subclasses
# of the File class.
def self.copyparam(klass, name)
param = klass.attrclass(name)
unless param
raise Puppet::DevError, "Class %s has no param %s" % [klass, name]
end
@parameters << param
@parameters.each { |p| @paramhash[name] = p }
if param.isnamevar?
@namevar = param.name
end
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, &block)
@@metaparams ||= []
@@metaparamhash ||= {}
name = symbolize(name)
param = genclass(name,
:parent => Puppet::Parameter,
:prefix => "MetaParam",
:hash => @@metaparamhash,
:array => @@metaparams,
&block
)
param.ismetaparameter
return param
end
def self.eachmetaparam
@@metaparams.each { |p| yield p.name }
end
# Find the default provider.
def self.defaultprovider
unless defined? @defaultprovider and @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.defaultnum }.max
defaults = defaults.find_all { |provider| provider.defaultnum == max }
retval = nil
if defaults.length > 1
Puppet.warning(
"Found multiple default providers for %s: %s; using %s" %
[self.name, defaults.collect { |i| i.name.to_s }.join(", "),
defaults[0].name]
)
retval = defaults.shift
elsif defaults.length == 1
retval = defaults.shift
else
raise Puppet::DevError, "Could not find a default provider for %s" %
self.name
end
@defaultprovider = retval
end
return @defaultprovider
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.
unless @providers.has_key?(name)
@providerloader.load(name)
end
return @providers[name]
end
# Just list all of the providers.
def self.providers
@providers.keys
end
def self.validprovider?(name)
name = Puppet::Util.symbolize(name)
return (@providers.has_key?(name) && @providers[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)
model = self
parent = if pname = options[:parent]
if pname.is_a? Class
pname
else
if provider = self.provider(pname)
provider
else
raise Puppet::DevError,
"Could not find parent provider %s of %s" %
[pname, name]
end
end
else
Puppet::Type::Provider
end
self.providify
provider = genclass(name,
:parent => parent,
:hash => @providers,
:prefix => "Provider",
:block => block,
:attributes => {
:model => model
}
)
return 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
model = self
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|
"* **%s**: %s" % [i, parenttype().provider(i).doc]
}.join("\n")
end
defaultto { @parent.class.defaultprovider.name }
validate do |value|
value = value[0] if value.is_a? Array
if provider = @parent.class.provider(value)
unless provider.suitable?
raise ArgumentError,
"Provider '%s' is not functional on this platform" %
[value]
end
else
raise ArgumentError, "Invalid %s provider '%s'" %
[@parent.class.name, value]
end
end
munge do |provider|
provider = provider[0] if provider.is_a? Array
if provider.is_a? String
provider = provider.intern
end
@parent.provider = provider
provider
end
end.parenttype = self
end
def self.unprovide(name)
if @providers.has_key? name
if @defaultprovider and @defaultprovider.name == name
@defaultprovider = nil
end
@providers.delete(name)
end
end
# Return an array of all of the suitable providers.
def self.suitableprovider
@providers.find_all { |name, provider|
provider.suitable?
}.collect { |name, provider|
provider
}
end
def provider=(name)
if klass = self.class.provider(name)
@provider = klass.new(self)
else
raise UnknownProviderError, "Could not find %s provider of %s" %
[name, self.class.name]
end
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)
param = genclass(name,
:parent => options[:parent] || Puppet::Parameter,
:attributes => { :element => self },
:block => block,
:prefix => "Parameter",
:array => @parameters,
:hash => @paramhash
)
# These might be enabled later.
# define_method(name) do
# @parameters[name].value
# end
#
# define_method(name.to_s + "=") do |value|
# newparam(param, value)
# end
if param.isnamevar?
@namevar = param.name
end
return param
end
# Create a new state. The first parameter must be the name of the state;
# this is how users will refer to the state when creating new instances.
# The second parameter is a hash of options; the options are:
# * :parent: The parent class for the state. Defaults to Puppet::State.
# * :retrieve: The method to call on the provider or @parent object (if
# the provider is not set) to retrieve the current value.
def self.newstate(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 %s" % options.inspect
end
if @validstates.include?(name)
raise Puppet::DevError, "Class %s already has a state named %s" %
[self.name, name]
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.
s = genclass(name,
:parent => options[:parent] || Puppet::State,
:hash => @validstates
) do
# If they've passed a retrieve method, then override the retrieve
# method on the class.
if options[:retrieve]
define_method(:retrieve) do
instance_variable_set(
"@is", provider.send(options[:retrieve])
)
end
end
if block
class_eval(&block)
end
end
# If it's the 'ensure' state, always put it first.
if name == :ensure
@states.unshift s
else
@states << s
end
# define_method(name) do
# @states[name].should
# end
#
# define_method(name.to_s + "=") do |value|
# newstate(name, :should => value)
# end
return s
end
# 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
# Return the parameter names
def self.parameters
return [] unless defined? @parameters
@parameters.collect { |klass| klass.name }
end
# Find the metaparameter class associated with a given metaparameter name.
def self.metaparamclass(name)
@@metaparamhash[symbolize(name)]
end
# Find the parameter class associated with a given parameter name.
def self.paramclass(name)
@paramhash[name]
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 :state: @validstates[name]
when :meta: @@metaparamhash[name]
when :param: @paramhash[name]
end
end
@attrclasses[name]
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
# does the name reflect a valid state?
def self.validstate?(name)
name = name.intern if name.is_a? String
if @validstates.include?(name)
return @validstates[name]
else
return false
end
end
# Return the list of validstates
def self.validstates
return {} unless defined? @states
return @validstates.keys
end
# Return the state class associated with a name
def self.statebyname(name)
@validstates[name]
end
# does the name reflect a valid parameter?
def self.validparameter?(name)
unless defined? @parameters
raise Puppet::DevError, "Class %s has not defined parameters" % self
end
if @paramhash.include?(name) or @@metaparamhash.include?(name)
return true
else
return false
end
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 @validstates.include?(attr): :state
when @@metaparamhash.include?(attr): :meta
when @paramhash.include?(attr): :param
else
raise Puppet::DevError,
"Invalid attribute '%s' for class '%s'" %
[attr, self.name]
end
end
@attrtypes[attr]
end
# All parameters, in the appropriate order. The namevar comes first,
# then the states, then the params and metaparams in the order they
# were specified in the files.
def self.allattrs
# now get all of the arguments, in a specific order
# Cache this, since it gets called so many times
namevar = self.namevar
order = [namevar]
order << [self.states.collect { |state| state.name },
self.parameters,
self.metaparams].flatten.reject { |param|
# we don't want our namevar in there multiple times
param == namevar
}
order.flatten!
return order
end
# A similar function but one that yields the name, type, and class.
# This is mainly so that setdefaults doesn't call quite so many functions.
def self.eachattr(*ary)
# now get all of the arguments, in a specific order
# Cache this, since it gets called so many times
if ary.empty?
ary = nil
end
self.states.each { |state|
yield(state, :state) if ary.nil? or ary.include?(state.name)
}
@parameters.each { |param|
yield(param, :param) if ary.nil? or ary.include?(param.name)
}
@@metaparams.each { |param|
yield(param, :meta) if ary.nil? or ary.include?(param.name)
}
end
def self.validattr?(name)
if name.is_a?(String)
name = name.intern
end
if self.validstate?(name) or self.validparameter?(name) or self.metaparam?(name)
return true
else
return false
end
end
# abstract accessing parameters and states, 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(:state)' or 'object.should(:state)'.
def [](name)
if name.is_a?(String)
name = name.intern
end
if name == :name
name = self.class.namevar
end
case self.class.attrtype(name)
when :state
if @states.include?(name)
return @states[name].is
else
return nil
end
when :meta
if @metaparams.include?(name)
return @metaparams[name].value
else
if default = self.class.metaparamclass(name).default
return default
else
return nil
end
end
when :param
if @parameters.include?(name)
return @parameters[name].value
else
if default = self.class.paramclass(name).default
return default
else
return nil
end
end
else
raise TypeError.new("Invalid parameter %s(%s)" % [name, name.inspect])
end
end
# Abstract setting parameters and states, and normalize
# access to always be symbols, not strings. This sets the 'should'
# value on states, and otherwise just sets the appropriate parameter.
def []=(name,value)
if name.is_a?(String)
name = name.intern
end
if name == :name
name = self.class.namevar
end
if value.nil?
raise Puppet::Error.new("Got nil value for %s" % name)
end
case self.class.attrtype(name)
when :state
if value.is_a?(Puppet::State)
self.debug "'%s' got handed a state for '%s'" % [self,name]
@states[name] = value
else
if @states.include?(name)
@states[name].should = value
else
# newstate returns true if it successfully created the state,
# false otherwise; I just don't know what to do with that
# fact.
unless newstate(name, :should => value)
#self.info "%s failed" % name
end
end
end
when :meta
self.newmetaparam(self.class.metaparamclass(name), value)
when :param
klass = self.class.attrclass(name)
# if they've got a method to handle the parameter, then do it that way
self.newparam(klass, value)
else
raise Puppet::Error, "Invalid parameter %s" % [name]
end
end
# remove a state from the object; useful in testing or in cleanup
# when an error has been encountered
def delete(attr)
case attr
when Puppet::Type
if @children.include?(attr)
@children.delete(attr)
end
else
if @states.has_key?(attr)
@states.delete(attr)
elsif @parameters.has_key?(attr)
@parameters.delete(attr)
elsif @metaparams.has_key?(attr)
@metaparams.delete(attr)
else
raise Puppet::DevError.new("Undefined attribute '#{attr}' in #{self}")
end
end
end
# iterate across all children, and then iterate across states
# we do children first so we're sure that all dependent objects
# are checked first
# we ignore parameters here, because they only modify how work gets
# done, they don't ever actually result in work specifically
def each
# we want to return the states in the order that each type
# specifies it, because it may (as in the case of File#create)
# be important
if self.class.depthfirst?
@children.each { |child|
yield child
}
end
self.eachstate { |state|
yield state
}
unless self.class.depthfirst?
@children.each { |child|
yield child
}
end
end
# Recurse deeply through the tree, but only yield types, not states.
def delve(&block)
self.each do |obj|
if obj.is_a? Puppet::Type
obj.delve(&block)
end
end
block.call(self)
end
# iterate across the existing states
def eachstate
# states() is a private method
states().each { |state|
yield state
}
end
def devfail(msg)
self.fail(Puppet::DevError, msg)
end
# Throw an error, defaulting to a Puppet::Error
def fail(*args)
type = nil
if args[0].is_a?(Class)
type = args.shift
else
type = Puppet::Error
end
error = type.new(args.join(" "))
if defined? @line and @line
error.line = @line
end
if defined? @file and @file
error.file = @file
end
raise error
end
# retrieve the 'is' value for a specified state
def is(state)
if @states.include?(state)
return @states[state].is
else
return nil
end
end
# retrieve the 'should' value for a specified state
def should(state)
if @states.include?(state)
return @states[state].should
else
return nil
end
end
# create a log at specified level
def log(msg)
Puppet::Log.create(
:level => @metaparams[:loglevel].value,
:message => msg,
:source => self
)
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 defined? @managed and @managed
return @managed
else
@managed = false
states.each { |state|
if state.should and ! state.class.unmanaged
@managed = true
break
end
}
return @managed
end
end
# Create a new parameter.
def newparam(klass, value = nil)
newattr(:param, klass, value)
end
# Create a new parameter or metaparameter. We'll leave the calling
# method to store it appropriately.
def newmetaparam(klass, value = nil)
newattr(:meta, klass, value)
end
# The base function that the others wrap.
def newattr(type, klass, value = nil)
# This should probably be a bit, um, different, but...
if type == :state
return newstate(klass)
end
param = klass.new
param.parent = self
unless value.nil?
param.value = value
end
case type
when :meta
@metaparams[klass.name] = param
when :param
@parameters[klass.name] = param
else
self.devfail("Invalid param type %s" % type)
end
return param
end
# create a new state
def newstate(name, hash = {})
stateklass = nil
if name.is_a?(Class)
stateklass = name
name = stateklass.name
else
stateklass = self.class.validstate?(name)
unless stateklass
self.fail("Invalid state %s" % name)
end
end
if @states.include?(name)
hash.each { |var,value|
@states[name].send(var.to_s + "=", value)
}
else
#Puppet.warning "Creating state %s for %s" %
# [stateklass.name,self.name]
begin
hash[:parent] = self
# make sure the state doesn't have any errors
newstate = stateklass.new(hash)
@states[name] = newstate
return newstate
rescue Puppet::Error => detail
# the state failed, so just ignore it
self.warning "State %s failed: %s" %
[name, detail]
return false
rescue Puppet::DevError => detail
# the state failed, so just ignore it
self.err "State %s failed: %s" %
[name, detail]
return false
rescue => detail
# the state failed, so just ignore it
self.err "State %s failed: %s (%s)" %
[name, detail, detail.class]
return false
end
end
end
# return the value of a parameter
def parameter(name)
unless name.is_a? Symbol
name = name.intern
end
return @parameters[name].value
end
def parent=(parent)
if self.parentof?(parent)
devfail "%s[%s] is already the parent of %s[%s]" %
[self.class.name, self.title, parent.class.name, parent.title]
end
@parent = parent
end
# Add a hook for testing for recursion.
def parentof?(child)
if (self == child)
debug "parent is equal to child"
return true
elsif defined? @parent and @parent.parentof?(child)
debug "My parent is parent of child"
return true
elsif @children.include?(child)
debug "child is already in children array"
return true
else
return false
end
end
def push(*childs)
unless defined? @children
@children = []
end
childs.each { |child|
# Make sure we don't have any loops here.
if parentof?(child)
devfail "Already the parent of %s[%s]" % [child.class.name, child.title]
end
unless child.is_a?(Puppet::Element)
self.debug "Got object of type %s" % child.class
self.devfail(
"Containers can only contain Puppet::Elements, not %s" %
child.class
)
end
@children.push(child)
child.parent = self
}
end
# Remove an object. The argument determines whether the object's
# subscriptions get eliminated, too.
def remove(rmdeps = true)
# Our children remove themselves from our @children array (else the object
# we called this on at the top would not be removed), so we duplicate the
# array and iterate over that. If we don't do this, only half of the
# objects get removed.
@children.dup.each { |child|
child.remove(rmdeps)
}
@children.clear
# This is hackish (mmm, cut and paste), but it works for now, and it's
# better than warnings.
[@states, @parameters, @metaparams].each do |hash|
hash.each do |name, obj|
obj.remove
end
hash.clear
end
if rmdeps
Puppet::Event::Subscription.dependencies(self).each { |dep|
#info "Deleting dependency %s" % dep
#begin
# self.unsubscribe(dep)
#rescue
# # ignore failed unsubscribes
#end
dep.delete
}
Puppet::Event::Subscription.subscribers(self).each { |dep|
#info "Unsubscribing from %s" % dep
begin
dep.unsubscribe(self)
rescue
# ignore failed unsubscribes
end
}
end
self.class.delete(self)
if defined? @parent and @parent
@parent.delete(self)
@parent = nil
end
# Remove the reference to the provider.
if self.provider
@provider.clear
@provider = nil
end
end
# Is the named state defined?
def statedefined?(name)
unless name.is_a? Symbol
name = name.intern
end
return @states.include?(name)
end
# return an actual type by name; to return the value, use 'inst[name]'
# FIXME this method should go away
def state(name)
unless name.is_a? Symbol
name = name.intern
end
return @states[name]
end
private
def states
#debug "%s has %s states" % [self,@states.length]
tmpstates = []
self.class.states.each { |state|
if @states.include?(state.name)
tmpstates.push(@states[state.name])
end
}
unless tmpstates.length == @states.length
self.devfail(
"Something went very wrong with tmpstates creation"
)
end
return tmpstates
end
# instance methods related to instance intrinsics
# e.g., initialize() and name()
public
# Force users to call this, so that we can merge objects if
# necessary. FIXME This method should be responsible for most of the
# error handling.
def self.create(args)
# Don't modify the original hash; instead, create a duplicate and modify it.
# We have to dup and use the ! so that it stays a TransObject if it is
# one.
hash = args.dup
symbolizehash!(hash)
# If we're the base class, then pass the info on appropriately
if self == Puppet::Type
type = nil
if hash.is_a? TransObject
type = hash.type
else
# If we're using the type to determine object type, then delete it
if type = hash[:type]
hash.delete(:type)
end
end
if type
if typeklass = self.type(type)
return typeklass.create(hash)
else
raise Puppet::Error, "Unknown type %s" % type
end
else
raise Puppet::Error, "No type found for %s" % hash.inspect
end
end
# Handle this new object being implicit
implicit = hash[:implicit] || false
if hash.include?(:implicit)
hash.delete(:implicit)
end
name = nil
unless hash.is_a? TransObject
hash = self.hash2trans(hash)
end
# XXX This will have to change when transobjects change to using titles
title = hash.name
#Puppet.debug "Creating %s[%s]" % [self.name, title]
# if the object already exists
if self.isomorphic? and retobj = self[title]
# if only one of our objects is implicit, then it's easy to see
# who wins -- the non-implicit one.
if retobj.implicit? and ! implicit
Puppet.notice "Removing implicit %s" % retobj.title
# Remove all of the objects, but do not remove their subscriptions.
retobj.remove(false)
# now pass through and create the new object
elsif implicit
Puppet.notice "Ignoring implicit %s" % title
return retobj
else
# If only one of the objects is being managed, then merge them
if retobj.managed?
raise Puppet::Error, "%s '%s' is already being managed" %
[self.name, title]
else
retobj.merge(hash)
return retobj
end
# We will probably want to support merging of some kind in
# the future, but for now, just throw an error.
#retobj.merge(hash)
#return retobj
end
end
# create it anew
# if there's a failure, destroy the object if it got that far, but raise
# the error.
begin
obj = new(hash)
rescue => detail
Puppet.err "Could not create %s: %s" % [title, detail.to_s]
if obj
obj.remove(true)
elsif obj = self[title]
obj.remove(true)
end
raise
end
if implicit
obj.implicit = true
end
# Store the object by title
self[obj.title] = obj
return obj
end
# Convert a hash to a TransObject.
def self.hash2trans(hash)
title = nil
if hash.include? :title
title = hash[:title]
hash.delete(:title)
elsif hash.include? self.namevar
title = hash[self.namevar]
hash.delete(self.namevar)
if hash.include? :name
raise ArgumentError, "Cannot provide both name and %s to %s" %
[self.namevar, self.name]
end
elsif hash[:name]
title = hash[:name]
hash.delete :name
end
unless title
raise Puppet::Error,
"You must specify a title for objects of type %s" % self.to_s
end
if hash.include? :type
unless self.validattr? :type
hash.delete :type
end
end
# okay, now make a transobject out of hash
begin
trans = TransObject.new(title, self.name.to_s)
hash.each { |param, value|
trans[param] = value
}
rescue => detail
raise Puppet::Error, "Could not create %s: %s" %
[name, detail]
end
return trans
end
def self.implicitcreate(hash)
unless hash.include?(:implicit)
hash[:implicit] = true
end
if obj = self.create(hash)
obj.implicit = true
return obj
else
return nil
end
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
# and then make 'new' private
class << self
private :new
end
def initvars
@children = []
@evalcount = 0
@tags = []
# callbacks are per object and event
@callbacks = Hash.new { |chash, key|
chash[key] = {}
}
# states and parameters are treated equivalently from the outside:
# as name-value pairs (using [] and []=)
# internally, however, parameters are merely a hash, while states
# point to State objects
# further, the lists of valid states and parameters are defined
# at the class level
unless defined? @states
@states = Hash.new(false)
end
unless defined? @parameters
@parameters = Hash.new(false)
end
unless defined? @metaparams
@metaparams = Hash.new(false)
end
# set defalts
@noop = false
# keeping stats for the total number of changes, and how many were
# completely sync'ed
# this isn't really sufficient either, because it adds lots of special
# cases such as failed changes
# it also doesn't distinguish between changes from the current transaction
# vs. changes over the process lifetime
@totalchanges = 0
@syncedchanges = 0
@failedchanges = 0
@inited = true
end
# initialize the type instance
def initialize(hash)
unless defined? @inited
self.initvars
end
namevar = self.class.namevar
orighash = hash
# If we got passed a transportable object, we just pull a bunch of info
# directly from it. This is the main object instantiation mechanism.
if hash.is_a?(Puppet::TransObject)
#self[:name] = hash[:name]
[:file, :line, :tags].each { |getter|
if hash.respond_to?(getter)
setter = getter.to_s + "="
if val = hash.send(getter)
self.send(setter, val)
end
end
}
# XXX This will need to change when transobjects change to titles.
@title = hash.name
hash = hash.to_hash
elsif hash[:title]
# XXX This should never happen
@title = hash[:title]
hash.delete(:title)
end
# Before anything else, set our parent if it was included
if hash.include?(:parent)
@parent = hash[:parent]
hash.delete(:parent)
end
# Munge up the namevar stuff so we only have one value.
hash = self.argclean(hash)
# If we've got both a title via some other mechanism, set it as an alias.
# if defined? @title and @title and ! hash[:name]
# if aliases = hash[:alias]
# aliases = [aliases] unless aliases.is_a? Array
# aliases << @title
# hash[:alias] = aliases
# else
# hash[:alias] = @title
# end
# end
# Let's do the name first, because some things need to happen once
# we have the name but before anything else
attrs = self.class.allattrs
if hash.include?(namevar)
#self.send(namevar.to_s + "=", hash[namevar])
self[namevar] = hash[namevar]
hash.delete(namevar)
if attrs.include?(namevar)
attrs.delete(namevar)
else
self.devfail "My namevar isn\'t a valid attribute...?"
end
else
self.devfail "I was not passed a namevar"
end
# If the name and title differ, set up an alias
if self.name != self.title
if obj = self.class[self.name]
if self.class.isomorphic?
raise Puppet::Error, "%s already exists with name %s" %
[obj.title, self.name]
end
else
self.class.alias(self.name, self)
end
end
# The information to cache to disk. We have to do this after
# the name is set because it uses the name and/or path, but before
# everything else is set because the states need to be able to
# retrieve their stored info.
#@cache = Puppet::Storage.cache(self)
# This is all of our attributes except the namevar.
attrs.each { |attr|
if hash.include?(attr)
begin
self[attr] = hash[attr]
rescue ArgumentError, Puppet::Error, TypeError
raise
rescue => detail
self.devfail(
"Could not set %s on %s: %s" %
[attr, self.class.name, detail]
)
end
hash.delete attr
end
}
# While this could theoretically be set after all of the objects are
# created, it seems to make more sense to set them immediately.
self.setdefaults
if hash.length > 0
self.debug hash.inspect
self.fail("Class %s does not accept argument(s) %s" %
[self.class.name, hash.keys.join(" ")])
end
if self.respond_to?(:validate)
self.validate
end
end
# Figure out of there are any objects we can automatically add as
# dependencies.
def autorequire
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)
# Retrieve the list of names from the block.
next unless list = self.instance_eval(&block)
unless list.is_a?(Array)
list = [list]
end
# Collect the current prereqs
list.each { |dep|
obj = nil
# Support them passing objects directly, to save some effort.
if dep.is_a? Puppet::Type
type = dep.class.name
obj = dep
# Now change our dependency to just the string, instead of
# the object itself.
dep = dep.title
else
# Skip autorequires that we aren't managing
unless obj = typeobj[dep]
next
end
end
# Skip autorequires that we already require
next if self.requires?(obj)
debug "Autorequiring %s %s" % [obj.class.name, obj.title]
self[:require] = [type, dep]
}
#self.info reqs.inspect
#self[:require] = reqs
}
end
# Set up all of our autorequires.
def finish
self.autorequire
# Scheduling has to be done when the whole config is instantiated, so
# that file order doesn't matter in finding them.
self.schedule
end
# Return a cached value
def cached(name)
Puppet::Storage.cache(self)[name]
#@cache[name] ||= nil
end
# Cache a value
def cache(name, value)
Puppet::Storage.cache(self)[name] = value
#@cache[name] = value
end
# Look up the schedule and set it appropriately. This is done after
# the instantiation phase, so that the schedule can be anywhere in the
# file.
def schedule
# If we've already set the schedule, then just move on
return if self[:schedule].is_a?(Puppet.type(:schedule))
return unless self[:schedule]
# Schedules don't need to be scheduled
#return if self.is_a?(Puppet.type(:schedule))
# Nor do components
#return if self.is_a?(Puppet.type(:component))
if sched = Puppet.type(:schedule)[self[:schedule]]
self[:schedule] = sched
else
self.fail "Could not find schedule %s" % self[:schedule]
end
end
# Check whether we are scheduled to run right now or not.
def scheduled?
return true if Puppet[:ignoreschedules]
return true unless schedule = self[:schedule]
# We use 'checked' here instead of 'synced' because otherwise we'll
# end up checking most elements most times, because they will generally
# have been synced a long time ago (e.g., a file only gets updated
# once a month on the server and its schedule is daily; the last sync time
# will have been a month ago, so we'd end up checking every run).
return schedule.match?(self.cached(:checked).to_i)
end
# Add a new tag.
def tag(tag)
tag = tag.intern if tag.is_a? String
unless @tags.include? tag
@tags << tag
end
end
# Define the initial list of tags.
def tags=(list)
list = [list] unless list.is_a? Array
@tags = list.collect do |t|
case t
when String: t.intern
when Symbol: t
else
self.warning "Ignoring tag %s of type %s" % [tag.inspect, tag.class]
end
end
@tags << self.class.name unless @tags.include?(self.class.name)
end
# Figure out of any of the specified tags apply to this object. This is an
# OR operation.
def tagged?(tags)
tags = [tags] unless tags.is_a? Array
tags = tags.collect { |t| t.intern }
return tags.find { |tag| @tags.include? tag }
end
# Is the specified parameter set?
def attrset?(type, attr)
case type
when :state: return @states.include?(attr)
when :param: return @parameters.include?(attr)
when :meta: return @metaparams.include?(attr)
else
self.devfail "Invalid set type %s" % [type]
end
end
# def set(name, value)
# send(name.to_s + "=", value)
# end
#
# def get(name)
# send(name)
# end
# For any parameters or states that have defaults and have not yet been
# set, set them now.
def setdefaults(*ary)
self.class.eachattr(*ary) { |klass, type|
# not many attributes will have defaults defined, so we short-circuit
# those away
next unless klass.method_defined?(:default)
next if self.attrset?(type, klass.name)
obj = self.newattr(type, klass)
value = obj.default
unless value.nil?
#self.debug "defaulting %s to %s" % [obj.name, obj.default]
obj.value = value
else
#self.debug "No default for %s" % obj.name
# "obj" is a Parameter.
self.delete(obj.name)
end
}
end
# Merge new information with an existing object, checking for conflicts
# and such. This allows for two specifications of the same object and
# the same values, but it's pretty limited right now. The result of merging
# states is very different from the result of merging parameters or metaparams.
# This is currently unused.
def merge(hash)
hash.each { |param, value|
if param.is_a?(String)
param = param.intern
end
# Of course names are the same, duh.
next if param == :name or param == self.class.namevar
unless value.is_a?(Array)
value = [value]
end
if @states.include?(param) and oldvals = @states[param].shouldorig
unless oldvals.is_a?(Array)
oldvals = [oldvals]
end
# If the values are exactly the same, order and everything,
# then it's okay.
if oldvals == value
return true
end
# take the intersection
newvals = oldvals & value
if newvals.empty?
self.fail "No common values for %s on %s(%s)" %
[param, self.class.name, self.title]
elsif newvals.length > 1
self.fail "Too many values for %s on %s(%s)" %
[param, self.class.name, self.title]
else
self.debug "Reduced old values %s and new values %s to %s" %
[oldvals.inspect, value.inspect, newvals.inspect]
@states[param].should = newvals
#self.should = newvals
return true
end
else
self[param] = value
end
}
# Set the defaults again, just in case.
self.setdefaults
end
# For now, leave the 'name' method functioning like it used to. Once 'title'
# works everywhere, I'll switch it.
def name
return self[:name]
# unless defined? @name and @name
# namevar = self.class.namevar
# if self.class.validparameter?(namevar)
# @name = self[:name]
# elsif self.class.validstate?(namevar)
# @name = self.should(namevar)
# else
# self.devfail "Could not find namevar %s for %s" %
# [namevar, self.class.name]
# end
# end
#
# unless @name
# self.devfail "Could not find namevar '%s' for %s" %
# [self.class.namevar, self.class.name]
# end
#
# return @name
end
# Retrieve the title of an object. If no title was set separately,
# then use the object's name.
def title
unless defined? @title and @title
namevar = self.class.namevar
if self.class.validparameter?(namevar)
@title = self[:name]
elsif self.class.validstate?(namevar)
@title = self.should(namevar)
else
self.devfail "Could not find namevar %s for %s" %
[namevar, self.class.name]
end
end
return @title
end
# fix any namevar => param translations
def argclean(oldhash)
# This duplication is here because it might be a transobject.
hash = oldhash.dup.to_hash
if hash.include?(:parent)
hash.delete(:parent)
end
namevar = self.class.namevar
# Do a simple translation for those cases where they've passed :name
# but that's not our namevar
if hash.include? :name and namevar != :name
if hash.include? namevar
raise ArgumentError, "Cannot provide both name and %s" % namevar
end
hash[namevar] = hash[:name]
hash.delete(:name)
end
# Make sure we have a name, one way or another
unless hash.include? namevar
if defined? @title and @title
hash[namevar] = @title
else
raise Puppet::Error,
"Was not passed a namevar or title"
end
end
return hash
end
# retrieve the current value of all contained states
def retrieve
# it's important to use the method here, as it follows the order
# in which they're defined in the object
states().each { |state|
state.retrieve
}
end
# convert to a string
def to_s
self.title
end
# Convert to a transportable object
def to_trans
# Collect all of the "is" values
retrieve()
trans = TransObject.new(self.title, self.class.name)
states().each do |state|
trans[state.name] = state.is
end
@parameters.each do |name, param|
# Avoid adding each instance name as both the name and the namevar
next if param.class.isnamevar? and param.value == self.title
trans[name] = param.value
end
@metaparams.each do |name, param|
trans[name] = param.value
end
trans.tags = self.tags
# FIXME I'm currently ignoring 'parent' and 'path'
return trans
end
# instance methods dealing with actually doing work
public
# this is a retarded hack method to get around the difference between
# component children and file children
def self.depthfirst?
if defined? @depthfirst
return @depthfirst
else
return false
end
end
# Retrieve the changes associated with all of the states.
def statechanges
# If we are changing the existence of the object, then none of
# the other states matter.
changes = []
if @states.include?(:ensure) and ! @states[:ensure].insync?
#self.info "ensuring %s from %s" %
# [@states[:ensure].should, @states[:ensure].is]
changes = [Puppet::StateChange.new(@states[:ensure])]
# Else, if the 'ensure' state is correctly absent, then do
# nothing
elsif @states.include?(:ensure) and @states[:ensure].is == :absent
#self.info "Object is correctly absent"
return []
else
#if @states.include?(:ensure)
# self.info "ensure: Is: %s, Should: %s" %
# [@states[:ensure].is, @states[:ensure].should]
#else
# self.info "no ensure state"
#end
changes = states().find_all { |state|
! state.insync?
}.collect { |state|
Puppet::StateChange.new(state)
}
end
if Puppet[:debug] and changes.length > 0
self.debug("Changing " + changes.collect { |ch|
ch.state.name
}.join(",")
)
end
changes
end
# this method is responsible for collecting state changes
# we always descend into the children before we evaluate our current
# states
# this returns any changes resulting from testing, thus 'collect'
# rather than 'each'
def evaluate
now = Time.now
#Puppet.err "Evaluating %s" % self.path.join(":")
unless defined? @evalcount
self.err "No evalcount defined on '%s' of type '%s'" %
[self.title,self.class]
@evalcount = 0
end
@evalcount += 1
changes = []
# this only operates on states, not states + children
# it's important that we call retrieve() on the type instance,
# not directly on the state, because it allows the type to override
# the method, like pfile does
self.retrieve
# states() is a private method, returning an ordered list
unless self.class.depthfirst?
changes += statechanges()
end
changes << @children.collect { |child|
ch = child.evaluate
child.cache(:checked, now)
ch
}
if self.class.depthfirst?
changes += statechanges()
end
changes.flatten!
# now record how many changes we've resulted in
if changes.length > 0
self.debug "%s change(s)" %
[changes.length]
end
self.cache(:checked, now)
return changes.flatten
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?
insync = true
if state = @states[:ensure]
if state.insync? and state.should == :absent
return true
end
end
states.each { |state|
unless state.insync?
state.debug("Not in sync: %s vs %s" %
[state.is.inspect, state.should.inspect])
insync = false
#else
# state.debug("In sync")
end
}
#self.debug("%s sync status is %s" % [self,insync])
return insync
end
# Meta-parameter methods: These methods deal with the results
# of specifying metaparameters
def self.metaparams
@@metaparams.collect { |param| param.name }
end
# Is the parameter in question a meta-parameter?
def self.metaparam?(param)
@@metaparamhash.include?(param)
end
# Subscription and relationship methods
#def addcallback(object, event, method)
# @callbacks[object][event] = method
#end
# Build the dependencies associated with an individual object.
def builddepends
# Handle the requires
if self[:require]
self.handledepends(self[:require], :NONE, nil, true)
end
# And the subscriptions
if self[:subscribe]
self.handledepends(self[:subscribe], :ALL_EVENTS, :refresh, true)
end
if self[:notify]
self.handledepends(self[:notify], :ALL_EVENTS, :refresh, false)
end
if self[:before]
self.handledepends(self[:before], :NONE, nil, false)
end
end
# return all objects that we depend on
def eachdependency
Puppet::Event::Subscription.dependencies(self).each { |dep|
yield dep.source
}
end
# return all objects subscribed to the current object
def eachsubscriber
Puppet::Event::Subscription.subscribers(self).each { |sub|
yield sub.target
}
end
def handledepends(requires, event, method, up)
# Requires are specified in the form of [type, name], so they're always
# an array. But we want them to be an array of arrays.
unless requires[0].is_a?(Array)
requires = [requires]
end
requires.each { |rname|
# we just have a name and a type, and we need to convert it
# to an object...
type = nil
object = nil
tname = rname[0]
unless type = Puppet::Type.type(tname)
self.fail "Could not find type %s" % tname.inspect
end
name = rname[1]
unless object = type[name]
self.fail "Could not retrieve object '%s' of type '%s'" %
[name,type]
end
self.debug("subscribes to %s" % [object])
# Are we requiring them, or vice versa?
source = target = nil
if up
source = object
target = self
else
source = self
target = object
end
# ok, both sides of the connection store some information
# we store the method to call when a given subscription is
# triggered, but the source object decides whether
subargs = {
:event => event,
:source => source,
:target => target
}
if method and target.respond_to?(method)
subargs[:callback] = method
end
Puppet::Event::Subscription.new(subargs)
}
end
def requires?(object)
req = false
self.eachdependency { |dep|
if dep == object
req = true
break
end
}
return req
end
def subscribe(hash)
hash[:source] = self
Puppet::Event::Subscription.new(hash)
# add to the correct area
#@subscriptions.push sub
end
def subscribesto?(object)
sub = false
self.eachsubscriber { |o|
if o == object
sub = true
break
end
}
return sub
end
# Unsubscribe from a given object, possibly with a specific event.
def unsubscribe(object, event = nil)
Puppet::Event::Subscription.dependencies(self).find_all { |sub|
if event
sub.match?(event)
else
sub.source == object
end
}.each { |sub|
sub.delete
}
end
# we've received an event
# we only support local events right now, so we can pass actual
# objects around, including the transaction object
# the assumption here is that container objects will pass received
# methods on to contained objects
# i.e., we don't trigger our children, our refresh() method calls
# refresh() on our children
def trigger(event, source)
trans = event.transaction
if @callbacks.include?(source)
[:ALL_EVENTS, event.event].each { |eventname|
if method = @callbacks[source][eventname]
if trans.triggered?(self, method) > 0
next
end
if self.respond_to?(method)
self.send(method)
end
trans.triggered(self, method)
end
}
end
end
# Documentation methods
def self.paramdoc(param)
@paramhash[param].doc
end
def self.metaparamdoc(metaparam)
@@metaparamhash[metaparam].doc
end
# Add all of the meta parameters.
#newmetaparam(:onerror) do
# desc "How to handle errors -- roll back innermost
# transaction, roll back entire transaction, ignore, etc. Currently
# non-functional."
#end
newmetaparam(:noop) do
desc "Boolean flag indicating whether work should actually
be done. *true*/**false**"
munge do |noop|
if noop == "true" or noop == true
return true
elsif noop == "false" or noop == false
return false
else
self.fail("Invalid noop value '%s'" % noop)
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:
schedule { daily:
period => daily,
range => \"2-4\"
}
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."
munge do |name|
if schedule = Puppet.type(:schedule)[name]
return schedule
else
return name
end
end
end
newmetaparam(:check) do
desc "States which should have their values retrieved
but which should not actually be modified. This is currently used
internally, but will eventually be used for querying, so that you
could specify that you wanted to check the install state of all
packages, and then query the Puppet client daemon to get reports
on all packages."
munge do |args|
# If they've specified all, collect all known states
if args == :all
args = @parent.class.states.collect do |state|
state.name
end
end
unless args.is_a?(Array)
args = [args]
end
unless defined? @parent
self.devfail "No parent for %s, %s?" %
[self.class, self.name]
end
args.each { |state|
unless state.is_a?(Symbol)
state = state.intern
end
next if @parent.statedefined?(state)
stateklass = @parent.class.validstate?(state)
unless stateklass
raise Puppet::Error, "%s is not a valid attribute for %s" %
[state, self.class.name]
end
next unless stateklass.checkable?
@parent.newstate(state)
}
end
end
# For each object we require, subscribe to all events that it generates. We
# might reduce the level of subscription eventually, but for now...
newmetaparam(:require) 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:
# 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\"]
}
Note that Puppet will autorequire everything that it can, and
there are hooks in place so that it's easy for elements 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 elements 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
file down would be automatically listed as a requirement to the
``exec`` code, so that you would always be running againts the
most recent version.
"
# Take whatever dependencies currently exist and add these.
# Note that this probably doesn't behave correctly with unsubscribe.
munge do |requires|
# We need to be two arrays deep...
unless requires.is_a?(Array)
requires = [requires]
end
unless requires[0].is_a?(Array)
requires = [requires]
end
if values = @parent[:require]
requires = values + requires
end
requires
end
end
# For each object we require, subscribe to all events that it generates.
# We might reduce the level of subscription eventually, but for now...
newmetaparam(:subscribe) 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
}
service { nagios:
running => true,
subscribe => file[nagconf]
}
}
"
munge do |requires|
if values = @parent[:subscribe]
requires = values + requires
end
requires
# @parent.handledepends(requires, :ALL_EVENTS, :refresh)
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::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 => \"...\"
}
service { sshd:
subscribe => file[sshdconfig]
}
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,
but note that only the library can use these aliases; for instance,
the following code will not work:
file { \"/etc/ssh/sshd_config\":
owner => root,
group => root,
alias => sshdconfig
}
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 [language tutorial][] for more information.
[language tutorial]: languagetutorial.html
"
munge do |aliases|
unless aliases.is_a?(Array)
aliases = [aliases]
end
@parent.info "Adding aliases %s" % aliases.collect { |a|
a.inspect
}.join(", ")
aliases.each do |other|
if obj = @parent.class[other]
unless obj == @parent
self.fail(
"%s can not create alias %s: object already exists" %
[@parent.title, other]
)
end
next
end
@parent.class.alias(other, @parent)
end
end
end
newmetaparam(:tag) do
desc "Add the specified tags to the associated element. While all elements
are automatically tagged with as much information as possible
(e.g., each class and component containing the element), it can
be useful to add your own tags to a given element.
Tags are currently useful for things like applying a subset of a
host's configuration:
puppetd --test --tag 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|
@parent.tag(tag)
end
end
end
newmetaparam(:notify) do
desc %{This parameter is the opposite of **subscribe** -- it sends events
to the specified object:
file { "/etc/sshd_config":
source => "....",
notify => service[sshd]
}
service { sshd:
ensure => running
}
This will restart the sshd service if the sshd config file changes.}
# Take whatever dependencies currently exist and add these.
munge do |notifies|
# We need to be two arrays deep...
unless notifies.is_a?(Array)
notifies = [notifies]
end
unless notifies[0].is_a?(Array)
notifies = [notifies]
end
if values = @parent[:notify]
notifies = values + notifies
end
notifies
end
end
newmetaparam(:before) do
desc %{This parameter is the opposite of **require** -- it guarantees
that the specified object is applied later than the specifying
object:
file { "/var/nagios/configuration":
source => "...",
recurse => true,
before => exec["nagios-rebuid"]
}
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.}
# Take whatever dependencies currently exist and add these.
munge do |notifies|
# We need to be two arrays deep...
unless notifies.is_a?(Array)
notifies = [notifies]
end
unless notifies[0].is_a?(Array)
notifies = [notifies]
end
if values = @parent[:notify]
notifies = values + notifies
end
notifies
end
end
end # Puppet::Type
end
require 'puppet/statechange'
require 'puppet/provider'
require 'puppet/type/component'
require 'puppet/type/cron'
require 'puppet/type/exec'
require 'puppet/type/group'
require 'puppet/type/package'
require 'puppet/type/pfile'
require 'puppet/type/pfilebucket'
require 'puppet/type/schedule'
require 'puppet/type/service'
require 'puppet/type/symlink'
require 'puppet/type/user'
require 'puppet/type/tidy'
require 'puppet/type/parsedtype'
# $Id$
diff --git a/lib/puppet/type/zone.rb b/lib/puppet/type/zone.rb
index de94e51ca..c882e199c 100644
--- a/lib/puppet/type/zone.rb
+++ b/lib/puppet/type/zone.rb
@@ -1,610 +1,612 @@
Puppet::Type.newtype(:zone) do
@doc = "Solaris zones."
# These states 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 ZoneConfigState < Puppet::State
# Perform the config operation.
def sync
@parent.cfg self.configtext
end
end
# Those states that can have multiple instances.
class ZoneMultiConfigState < ZoneConfigState
def configtext
list = @should
unless @is.is_a? Symbol
if @is.is_a? Array
list += @is
else
if @is
list << @is
end
end
end
# Some hackery so we can test whether @is is an array or a symbol
if @is.is_a? Array
tmpis = @is
else
if @is
tmpis = [@is]
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?
if @is.is_a? Array and @should.is_a? Array
@is.sort == @should.sort
else
@is == @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 = {}
def self.newvalue(name, hash)
if @parametervalues.is_a? Hash
@parametervalues = []
end
@parametervalues << name
@states[name] = hash
hash[:name] = name
end
newvalue :absent, :down => :destroy
newvalue :configured, :up => :configure, :down => :uninstall
newvalue :installed, :up => :install, :down => :stop
newvalue :running, :up => :start
defaultto :running
def self.valueindex(value)
@parametervalues.index(value)
end
# Return all of the states between two listed values, exclusive
# of the first item.
def self.valueslice(first, second)
findex = sindex = nil
unless findex = @parametervalues.index(first)
raise ArgumentError, "'%s' is not a valid zone state" % first
end
unless sindex = @parametervalues.index(second)
raise ArgumentError, "'%s' is not a valid zone state" % first
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 is=(value)
value = value.intern if value.is_a? String
@is = value
end
def sync
method = nil
if up?
dir = :up
else
dir = :down
end
# We need to get the state we're currently in and just call
# everything between it and us.
states = self.class.valueslice(self.is, self.should)
states.each do |st|
if method = st[dir]
warned = false
while @parent.processing?
unless warned
info "Waiting for zone to finish processing"
warned = true
end
sleep 1
end
@parent.send(method)
else
raise Puppet::DevError, "Cannot move %s from %s" %
[dir, st[:name]]
end
end
return ("zone_" + self.should.to_s).intern
end
# Are we moving up the state tree?
def up?
self.class.valueindex(self.is) < self.class.valueindex(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
newstate(:ip, :parent => ZoneMultiConfigState) 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."
validate do |value|
unless value =~ /:/
raise ArgumentError,
"IP addresses must specify the interface and the address, separated by a colon."
end
interface, address = value.split(':')
begin
IPAddr.new(address)
rescue ArgumentError
raise ArgumentError, "'%s' is an invalid IP address" % address
end
end
# Add a directory to our list of inherited directories.
def add(str)
interface, ip = ipsplit(str)
"add net
set address=#{ip}
set physical=#{interface}
end
"
end
# Convert a string into the component interface and address
def ipsplit(str)
interface, address = str.split(':')
return interface, address
end
def rm(str)
interface, ip = 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.
"remove net address=#{ip}"
end
end
newstate(:autoboot, :parent => ZoneConfigState) do
desc "Whether the zone should automatically boot."
defaultto true
newvalue(:true) {}
newvalue(:false) {}
def configtext
"set autoboot=#{self.should}"
end
end
newstate(:pool, :parent => ZoneConfigState) do
desc "The resource pool for this zone."
def configtext
"set pool=#{self.should}"
end
end
newstate(:shares, :parent => ZoneConfigState) 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
newstate(:inherit, :parent => ZoneMultiConfigState) 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, "The zone base 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 % @parent[:name]
else
value
end
end
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
# Convert the output of a list into a hash
def self.line2hash(line)
fields = [:id, :name, :ensure, :path]
hash = {}
line.split(":").each_with_index { |value, index|
hash[fields[index]] = value
}
# Configured but not installed zones do not have IDs
if hash[:id] == "-"
hash.delete(:id)
end
return hash
end
def self.list
%x{/usr/sbin/zoneadm list -cp}.split("\n").collect do |line|
hash = line2hash(line)
obj = nil
unless obj = @objects[hash[:name]]
obj = create(:name => hash[:name])
end
obj.setstatus(hash)
obj
end
end
# Execute a configuration string. Can't be private because it's called
# by the states.
def cfg(str)
debug "Executing '%s' in zone %s" % [str, self[:name]]
IO.popen("/usr/sbin/zonecfg -z %s -f - 2>&1" % self[:name], "w") do |pipe|
pipe.puts str
end
unless $? == 0
raise ArgumentError, "Failed to apply configuration"
end
end
# Perform all of our configuration steps.
def configure
# If the thing is entirely absent, then we need to create the config.
str = %{create -b
set zonepath=%s
} % self[:path]
# Then perform all of our configuration steps.
@states.each do |name, state|
if state.is_a? ZoneConfigState and ! state.insync?
str += state.configtext + "\n"
end
end
str += "commit\n"
cfg(str)
end
def destroy
begin
execute("/usr/sbin/zonecfg -z #{self[:name]} delete -F")
rescue Puppet::ExecutionFailure => detail
self.fail "Could not destroy zone: %s" % detail
end
end
def install
begin
execute("/usr/sbin/zoneadm -z #{self[:name]} install")
rescue Puppet::ExecutionFailure => detail
self.fail "Could not install zone: %s" % detail
end
end
# We need a way to test whether a zone is in process. Our 'ensure'
# state models the static states, but we need to handle the temporary ones.
def processing?
if hash = statushash()
case hash[:ensure]
when "incomplete", "ready", "shutting_down"
true
else
false
end
else
false
end
end
def retrieve
if hash = statushash()
setstatus(hash)
# Now retrieve the configuration itself and set appropriately.
getconfig()
else
@states.each do |name, state|
state.is = :absent
end
end
end
# Take the results of a listing and set everything appropriately.
def setstatus(hash)
hash.each do |param, value|
next if param == :name
case self.class.attrtype(param)
when :state:
self.is = [param, value]
else
self[param] = value
end
end
# For any configured items that are not found, mark absent.
@states.each do |name, st|
next unless st.is_a? ZoneConfigState
unless hash.has_key? st.name
st.is = :absent
end
end
end
def start
# Check the sysidcfg stuff
if cfg = self[:sysidcfg]
path = File.join(self[:path], "root", "etc", "sysidcfg")
unless File.exists?(path)
begin
File.open(path, "w", 0600) do |f|
f.puts cfg
end
rescue => detail
if Puppet[:debug]
puts detail.stacktrace
end
raise Puppet::Error, "Could not create sysidcfg: %s" % detail
end
end
end
begin
execute("/usr/sbin/zoneadm -z #{self[:name]} boot")
rescue Puppet::ExecutionFailure => detail
self.fail "Could not start zone: %s" % detail
end
end
def stop
begin
execute("/usr/sbin/zoneadm -z #{self[:name]} halt")
rescue Puppet::ExecutionFailure => detail
self.fail "Could not halt zone: %s" % detail
end
end
def unconfigure
begin
execute("/usr/sbin/zonecfg -z #{self[:name]} delete -F")
rescue Puppet::ExecutionFailure => detail
self.fail "Could not unconfigure zone: %s" % detail
end
end
def uninstall
begin
execute("/usr/sbin/zoneadm -z #{self[:name]} uninstall -F")
rescue Puppet::ExecutionFailure => detail
self.fail "Could not halt zone: %s" % detail
end
end
private
# Turn the results of getconfig into status information.
def config2status(config)
config.each do |name, value|
case name
when :autoboot:
self.is = [:autoboot, value.intern]
when :zonepath:
# Nothing; this is set in the zoneadm list command
when :pool:
self.is = [:pool, value]
when :shares:
self.is = [:shares, value]
when "inherit-pkg-dir":
dirs = value.collect do |hash|
hash[:dir]
end
self.is = [:inherit, dirs]
when "net":
vals = value.collect do |hash|
"%s:%s" % [hash[:physical], hash[:address]]
end
self.is = [:ip, vals]
end
end
end
# Collect the configuration of the zone.
def getconfig
output = execute("/usr/sbin/zonecfg -z %s info" % self[:name])
name = nil
current = nil
hash = {}
output.split("\n").each do |line|
case line
when /^(\S+):\s*$/:
name = $1
current = nil # reset it
when /^(\S+):\s*(.+)$/:
hash[$1.intern] = $2
#self.is = [$1.intern, $2]
when /^\s+(\S+):\s*(.+)$/:
if name
unless hash.include? name
hash[name] = []
end
unless current
current = {}
hash[name] << current
end
current[$1.intern] = $2
else
err "Ignoring '%s'" % line
end
else
debug "Ignoring zone output '%s'" % line
end
end
config2status(hash)
end
def statushash
begin
output = execute("/usr/sbin/zoneadm -z #{self[:name]} list -p 2>/dev/null")
rescue Puppet::ExecutionFailure => detail
return nil
end
return self.class.line2hash(output.chomp)
end
end
# $Id$
diff --git a/test/lib/puppettest.rb b/test/lib/puppettest.rb
index bff410060..4fa7a676f 100644
--- a/test/lib/puppettest.rb
+++ b/test/lib/puppettest.rb
@@ -1,199 +1,207 @@
require 'puppet'
require 'test/unit'
module PuppetTest
# Find the root of the Puppet tree; this is not the test directory, but
# the parent of that dir.
def basedir
unless defined? @@basedir
case $0
when /rake_test_loader/
@@basedir = File.dirname(Dir.getwd)
else
dir = nil
if /^#{File::SEPARATOR}.+\.rb/
dir = $0
else
dir = File.join(Dir.getwd, $0)
end
3.times { dir = File.dirname(dir) }
@@basedir = dir
end
end
@@basedir
end
def cleanup(&block)
@@cleaners << block
end
def datadir
File.join(basedir, "test", "data")
end
def exampledir(*args)
unless defined? @@exampledir
@@exampledir = File.join(basedir, "examples")
end
if args.empty?
return @@exampledir
else
return File.join(@@exampledir, *args)
end
end
module_function :basedir, :datadir, :exampledir
def rake?
$0 =~ /rake_test_loader/
end
def setup
@memoryatstart = Puppet::Util.memory
if defined? @@testcount
@@testcount += 1
else
@@testcount = 0
end
@configpath = File.join(tmpdir,
self.class.to_s + "configdir" + @@testcount.to_s + "/"
)
unless defined? $user and $group
$user = nonrootuser().uid.to_s
$group = nonrootgroup().gid.to_s
end
Puppet[:user] = $user
Puppet[:group] = $group
Puppet[:confdir] = @configpath
Puppet[:vardir] = @configpath
unless File.exists?(@configpath)
Dir.mkdir(@configpath)
end
@@tmpfiles = [@configpath, tmpdir()]
@@tmppids = []
@@cleaners = []
# If we're running under rake, then disable debugging and such.
if rake? and ! Puppet[:debug]
Puppet::Log.close
Puppet::Log.newdestination tempfile()
Puppet[:httplog] = tempfile()
else
Puppet::Log.newdestination :console
Puppet::Log.level = :debug
#$VERBOSE = 1
Puppet.info @method_name
end
#if $0 =~ /.+\.rb/ or Puppet[:debug]
# Puppet::Log.newdestination :console
# Puppet::Log.level = :debug
# #$VERBOSE = 1
# Puppet.info @method_name
#else
# Puppet::Log.close
# Puppet::Log.newdestination tempfile()
# Puppet[:httplog] = tempfile()
#end
Puppet[:ignoreschedules] = true
end
def tempfile
if defined? @@tmpfilenum
@@tmpfilenum += 1
else
@@tmpfilenum = 1
end
f = File.join(self.tmpdir(), self.class.to_s + "_" + @method_name +
@@tmpfilenum.to_s)
@@tmpfiles << f
return f
end
def tstdir
dir = tempfile()
Dir.mkdir(dir)
return dir
end
def tmpdir
unless defined? @tmpdir and @tmpdir
@tmpdir = case Facter["operatingsystem"].value
when "Darwin": "/private/tmp"
when "SunOS": "/var/tmp"
else
"/tmp"
end
@tmpdir = File.join(@tmpdir, "puppettesting")
unless File.exists?(@tmpdir)
FileUtils.mkdir_p(@tmpdir)
File.chmod(01777, @tmpdir)
end
end
@tmpdir
end
def teardown
stopservices
@@cleaners.each { |cleaner| cleaner.call() }
@@tmpfiles.each { |file|
if FileTest.exists?(file)
system("chmod -R 755 %s" % file)
system("rm -rf %s" % file)
end
}
@@tmpfiles.clear
@@tmppids.each { |pid|
%x{kill -INT #{pid} 2>/dev/null}
}
@@tmppids.clear
Puppet::Type.allclear
Puppet::Storage.clear
Puppet::Rails.clear
Puppet.clear
@memoryatend = Puppet::Util.memory
diff = @memoryatend - @memoryatstart
if diff > 1000
Puppet.info "%s#%s memory growth (%s to %s): %s" %
[self.class, @method_name, @memoryatstart, @memoryatend, diff]
end
# reset all of the logs
Puppet::Log.close
# Just in case there are processes waiting to die...
- Process.waitall
+ require 'timeout'
+
+ begin
+ Timeout::timeout(5) do
+ Process.waitall
+ end
+ rescue Timeout::Error
+ # just move on
+ end
if File.stat("/dev/null").mode & 007777 != 0666
File.open("/tmp/nullfailure", "w") { |f|
f.puts self.class
}
exit(74)
end
end
end
require 'puppettest/support'
require 'puppettest/filetesting'
require 'puppettest/fakes'
require 'puppettest/exetest'
require 'puppettest/parsertesting'
require 'puppettest/servertest'
# $Id$