diff --git a/lib/puppet/resource.rb b/lib/puppet/resource.rb
index 74d006386..570923dd7 100644
--- a/lib/puppet/resource.rb
+++ b/lib/puppet/resource.rb
@@ -1,456 +1,467 @@
require 'puppet'
require 'puppet/util/tagging'
require 'puppet/util/pson'
require 'puppet/parameter'
# The simplest resource class. Eventually it will function as the
# base class for all resource-like behaviour.
class Puppet::Resource
# This stub class is only needed for serialization compatibility with 0.25.x.
# Specifically, it exists to provide a compatibility API when using YAML
# serialized objects loaded from StoreConfigs.
Reference = Puppet::Resource
include Puppet::Util::Tagging
require 'puppet/resource/type_collection_helper'
include Puppet::Resource::TypeCollectionHelper
extend Puppet::Util::Pson
include Enumerable
attr_accessor :file, :line, :catalog, :exported, :virtual, :validate_parameters, :strict
attr_reader :type, :title
require 'puppet/indirector'
extend Puppet::Indirector
indirects :resource, :terminus_class => :ral
ATTRIBUTES = [:file, :line, :exported]
def self.from_pson(pson)
raise ArgumentError, "No resource type provided in pson data" unless type = pson['type']
raise ArgumentError, "No resource title provided in pson data" unless title = pson['title']
resource = new(type, title)
if params = pson['parameters']
params.each { |param, value| resource[param] = value }
end
if tags = pson['tags']
tags.each { |tag| resource.tag(tag) }
end
ATTRIBUTES.each do |a|
if value = pson[a.to_s]
resource.send(a.to_s + "=", value)
end
end
resource.exported ||= false
resource
end
def inspect
"#{@type}[#{@title}]#{to_hash.inspect}"
end
def to_pson_data_hash
data = ([:type, :title, :tags] + ATTRIBUTES).inject({}) do |hash, param|
next hash unless value = self.send(param)
hash[param.to_s] = value
hash
end
data["exported"] ||= false
params = self.to_hash.inject({}) do |hash, ary|
param, value = ary
# Don't duplicate the title as the namevar
next hash if param == namevar and value == title
hash[param] = Puppet::Resource.value_to_pson_data(value)
hash
end
data["parameters"] = params unless params.empty?
data
end
def self.value_to_pson_data(value)
if value.is_a? Array
value.map{|v| value_to_pson_data(v) }
elsif value.is_a? Puppet::Resource
value.to_s
else
value
end
end
def yaml_property_munge(x)
case x
when Hash
x.inject({}) { |h,kv|
k,v = kv
h[k] = self.class.value_to_pson_data(v)
h
}
else self.class.value_to_pson_data(x)
end
end
def to_pson(*args)
to_pson_data_hash.to_pson(*args)
end
# Proxy these methods to the parameters hash. It's likely they'll
# be overridden at some point, but this works for now.
%w{has_key? keys length delete empty? <<}.each do |method|
define_method(method) do |*args|
parameters.send(method, *args)
end
end
# Set a given parameter. Converts all passed names
# to lower-case symbols.
def []=(param, value)
validate_parameter(param) if validate_parameters
parameters[parameter_name(param)] = value
end
# Return a given parameter's value. Converts all passed names
# to lower-case symbols.
def [](param)
parameters[parameter_name(param)]
end
def ==(other)
return false unless other.respond_to?(:title) and self.type == other.type and self.title == other.title
return false unless to_hash == other.to_hash
true
end
# Compatibility method.
def builtin?
builtin_type?
end
# Is this a builtin resource type?
def builtin_type?
resource_type.is_a?(Class)
end
# Iterate over each param/value pair, as required for Enumerable.
def each
parameters.each { |p,v| yield p, v }
end
def include?(parameter)
super || parameters.keys.include?( parameter_name(parameter) )
end
# These two methods are extracted into a Helper
# module, but file load order prevents me
# from including them in the class, and I had weird
# behaviour (i.e., sometimes it didn't work) when
# I directly extended each resource with the helper.
def environment
Puppet::Node::Environment.new(@environment)
end
def environment=(env)
if env.is_a?(String) or env.is_a?(Symbol)
@environment = env
else
@environment = env.name
end
end
%w{exported virtual strict}.each do |m|
define_method(m+"?") do
self.send(m)
end
end
# Create our resource.
def initialize(type, title = nil, attributes = {})
@parameters = {}
# Set things like strictness first.
attributes.each do |attr, value|
next if attr == :parameters
send(attr.to_s + "=", value)
end
@type, @title = extract_type_and_title(type, title)
@type = munge_type_name(@type)
if @type == "Class"
@title = :main if @title == ""
@title = munge_type_name(@title)
end
if params = attributes[:parameters]
extract_parameters(params)
end
tag(self.type)
tag(self.title) if valid_tag?(self.title)
@reference = self # for serialization compatibility with 0.25.x
if strict? and ! resource_type
if @type == 'Class'
raise ArgumentError, "Could not find declared class #{title}"
else
raise ArgumentError, "Invalid resource type #{type}"
end
end
end
def ref
to_s
end
# Find our resource.
def resolve
return(catalog ? catalog.resource(to_s) : nil)
end
def resource_type
case type
when "Class"; known_resource_types.hostclass(title == :main ? "" : title)
when "Node"; known_resource_types.node(title)
else
Puppet::Type.type(type.to_s.downcase.to_sym) || known_resource_types.definition(type)
end
end
# Produce a simple hash of our parameters.
def to_hash
parse_title.merge parameters
end
def to_s
"#{type}[#{title}]"
end
def uniqueness_key
# Temporary kludge to deal with inconsistant use patters
h = self.to_hash
h[namevar] ||= h[:name]
h[:name] ||= h[namevar]
h.values_at(*key_attributes.sort_by { |k| k.to_s })
end
def key_attributes
return(resource_type.respond_to? :key_attributes) ? resource_type.key_attributes : [:name]
end
# Convert our resource to Puppet code.
def to_manifest
# Collect list of attributes to align => and move ensure first
attr = parameters.keys
attr_max = attr.inject(0) { |max,k| k.to_s.length > max ? k.to_s.length : max }
attr.sort!
if attr.first != :ensure && attr.include?(:ensure)
attr.delete(:ensure)
attr.unshift(:ensure)
end
attributes = attr.collect { |k|
v = parameters[k]
" %-#{attr_max}s => %s,\n" % [k, Puppet::Parameter.format_value_for_display(v)]
}.join
"%s { '%s':\n%s}" % [self.type.to_s.downcase, self.title, attributes]
end
def to_ref
ref
end
# Convert our resource to a RAL resource instance. Creates component
# instances for resource types that don't exist.
def to_ral
if typeklass = Puppet::Type.type(self.type)
return typeklass.new(self)
else
return Puppet::Type::Component.new(self)
end
end
# Translate our object to a backward-compatible transportable object.
def to_trans
if builtin_type? and type.downcase.to_s != "stage"
result = to_transobject
else
result = to_transbucket
end
result.file = self.file
result.line = self.line
result
end
def to_trans_ref
[type.to_s, title.to_s]
end
# Create an old-style TransObject instance, for builtin resource types.
def to_transobject
# Now convert to a transobject
result = Puppet::TransObject.new(title, type)
to_hash.each do |p, v|
if v.is_a?(Puppet::Resource)
v = v.to_trans_ref
elsif v.is_a?(Array)
v = v.collect { |av|
av = av.to_trans_ref if av.is_a?(Puppet::Resource)
av
}
end
# If the value is an array with only one value, then
# convert it to a single value. This is largely so that
# the database interaction doesn't have to worry about
# whether it returns an array or a string.
result[p.to_s] = if v.is_a?(Array) and v.length == 1
v[0]
else
v
end
end
result.tags = self.tags
result
end
def name
# this is potential namespace conflict
# between the notion of an "indirector name"
# and a "resource name"
[ type, title ].join('/')
end
def to_resource
self
end
def valid_parameter?(name)
resource_type.valid_parameter?(name)
end
def validate_parameter(name)
raise ArgumentError, "Invalid parameter #{name}" unless valid_parameter?(name)
end
def prune_parameters(options = {})
properties = resource_type.properties.map(&:name)
dup.collect do |attribute, value|
if value.to_s.empty? or Array(value).empty?
delete(attribute)
elsif value.to_s == "absent" and attribute.to_s != "ensure"
delete(attribute)
end
parameters_to_include = options[:parameters_to_include] || []
delete(attribute) unless properties.include?(attribute) || parameters_to_include.include?(attribute)
end
self
end
private
# Produce a canonical method name.
def parameter_name(param)
param = param.to_s.downcase.to_sym
if param == :name and n = namevar
param = namevar
end
param
end
# The namevar for our resource type. If the type doesn't exist,
# always use :name.
def namevar
if builtin_type? and t = resource_type and t.key_attributes.length == 1
t.key_attributes.first
else
:name
end
end
# Create an old-style TransBucket instance, for non-builtin resource types.
def to_transbucket
bucket = Puppet::TransBucket.new([])
bucket.type = self.type
bucket.name = self.title
# TransBuckets don't support parameters, which is why they're being deprecated.
bucket
end
def extract_parameters(params)
params.each do |param, value|
validate_parameter(param) if strict?
self[param] = value
end
end
def extract_type_and_title(argtype, argtitle)
if (argtitle || argtype) =~ /^([^\[\]]+)\[(.+)\]$/m then [ $1, $2 ]
elsif argtitle then [ argtype, argtitle ]
elsif argtype.is_a?(Puppet::Type) then [ argtype.class.name, argtype.title ]
elsif argtype.is_a?(Hash) then
raise ArgumentError, "Puppet::Resource.new does not take a hash as the first argument. "+
"Did you mean (#{(argtype[:type] || argtype["type"]).inspect}, #{(argtype[:title] || argtype["title"]).inspect }) ?"
else raise ArgumentError, "No title provided and #{argtype.inspect} is not a valid resource reference"
end
end
def munge_type_name(value)
return :main if value == :main
return "Class" if value == "" or value.nil? or value.to_s.downcase == "component"
value.to_s.split("::").collect { |s| s.capitalize }.join("::")
end
def parse_title
h = {}
type = resource_type
if type.respond_to? :title_patterns
type.title_patterns.each { |regexp, symbols_and_lambdas|
if captures = regexp.match(title.to_s)
- symbols_and_lambdas.zip(captures[1..-1]).each { |symbol_and_lambda,capture|
- sym, lam = symbol_and_lambda
- #self[sym] = lam.call(capture)
- h[sym] = lam.call(capture)
- }
+ symbols_and_lambdas.zip(captures[1..-1]).each do |symbol_and_lambda,capture|
+ symbol, proc = symbol_and_lambda
+ # Many types pass "identity" as the proc; we might as well give
+ # them a shortcut to delivering that without the extra cost.
+ #
+ # Especially because the global type defines title_patterns and
+ # uses the identity patterns.
+ #
+ # This was worth about 8MB of memory allocation saved in my
+ # testing, so is worth the complexity for the API.
+ if proc then
+ h[symbol] = proc.call(capture)
+ else
+ h[symbol] = capture
+ end
+ end
return h
end
}
# If we've gotten this far, then none of the provided title patterns
# matched. Since there's no way to determine the title then the
# resource should fail here.
raise Puppet::Error, "No set of title patterns matched the title \"#{title}\"."
else
return { :name => title.to_s }
end
end
def parameters
# @parameters could have been loaded from YAML, causing it to be nil (by
# bypassing initialize).
@parameters ||= {}
end
end
diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb
index 0f4988193..27f8cab43 100644
--- a/lib/puppet/type.rb
+++ b/lib/puppet/type.rb
@@ -1,1977 +1,1976 @@
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/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::FileCollection::Lookup
include Puppet::Util::Tagging
###############################
# Comparing type instances.
include Comparable
def <=>(other)
# We only order against other types, not arbitrary objects.
return nil unless other.is_a? Puppet::Type
# Our natural order is based on the reference name we use when comparing
# against other type instances.
self.ref <=> other.ref
end
###############################
# 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.
[:exists?, :create, :destroy].all? { |method|
self.public_method_defined?(method)
}
end
# These `apply_to` methods are horrible. They should really be implemented
# as part of the usual system of constraints that apply to a type and
# provider pair, but were implemented as a separate shadow system.
#
# We should rip them out in favour of a real constraint pattern around the
# target device - whatever that looks like - and not have this additional
# magic here. --daniel 2012-03-08
def self.apply_to_device
@apply_to = :device
end
def self.apply_to_host
@apply_to = :host
end
def self.apply_to_all
@apply_to = :both
end
def self.apply_to
@apply_to ||= :host
end
def self.can_apply_to(target)
[ target == :device ? :device : :host, :both ].include?(apply_to)
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
# This is a cache miss around 0.05 percent of the time. --daniel 2012-07-17
@key_attributes_cache ||= 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 ] ] ] ]
+ [ [ /(.*)/m, [ [key_attributes.first] ] ] ]
else
raise Puppet::DevError,"you must specify title patterns when there are two or more key attributes"
end
end
def uniqueness_key
self.class.key_attributes.sort_by { |attribute_name| attribute_name.to_s }.map{ |attribute_name| self[attribute_name] }
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 && nv = name_var
name = nv
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 && nv = name_var
name = nv
end
raise Puppet::Error.new("Got nil value for #{name}") if value.nil?
property = self.newattr(name)
if property
begin
# make sure the parameter doesn't have any errors
property.value = value
rescue => detail
error = Puppet::Error.new("Parameter #{name} failed on #{ref}: #{detail}")
error.set_backtrace(detail.backtrace)
raise error
end
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}.merge(options))
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
if provider and ! provider.class.supports_parameter?(klass)
missing = klass.required_features.find_all { |f| ! provider.class.feature?(f) }
debug "Provider %s does not support features %s; not managing attribute %s" % [provider.class.name, missing.join(", "), name]
return nil
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.
def depthfirst?
false
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.
def ancestors
[]
end
# 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.safe_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.safe_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|
all_properties = self.properties.find_all do |property|
provider.supports_parameter?(property)
end.collect do |property|
property.name
end
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
result = new(:name => instance.name, :provider => instance)
properties.each { |name| result.newattr(name) }
result
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 (can be nil), then the rest of the suitable providers.
sources = []
[defaultprovider, suitableprovider].flatten.uniq.collect do |provider|
next if provider.nil?
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:
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."
end
newmetaparam(:audit) do
desc "Marks a subset of this resource's unmanaged attributes for auditing. Accepts an
attribute name, an array of attribute names, or `all`.
Auditing a resource attribute has two effects: First, whenever a catalog
is applied with puppet apply or puppet agent, Puppet will check whether
that attribute of the resource has been modified, comparing its current
value to the previous run; any change will be logged alongside any actions
performed by Puppet while applying the catalog.
Secondly, marking a resource attribute for auditing will include that
attribute in inspection reports generated by puppet inspect; see the
puppet inspect documentation for more details.
Managed attributes for a resource can also be audited, but note that
changes made by Puppet will be logged as additional modifications. (I.e.
if a user manually edits a file whose contents are audited and managed,
puppet agent's next two runs will both log an audit notice: the first run
will log the user's edit and then revert the file to the desired state,
and the second run will log the edit made by Puppet.)"
validate do |list|
list = Array(list).collect {|p| p.to_sym}
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.kind_of?(Array) && list.to_sym == :all
list = all_properties
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 title:
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 title,
and the library sets that as an alias for the file so the dependency
lookup in `Service['sshd']` works. You can use this metaparameter 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 Guide](http://docs.puppetlabs.com/guides/language_guide.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.
Multiple tags can be specified as an array:
file {'/etc/hosts':
ensure => file,
source => 'puppet:///modules/site/hosts',
mode => 0644,
tag => ['bootstrap', 'minimumrun', 'mediumrun'],
}
Tags are useful for things like applying a subset of a host's configuration
with [the `tags` setting](/references/latest/configuration.html#tags):
puppet agent --test --tags bootstrap
This way, you can easily isolate the portion of the configuration you're
trying to test."
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 "References to 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\"]
}
Multiple dependencies can be specified by providing a comma-separated list
of resources, enclosed in square brackets:
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
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.
"
end
newmetaparam(:subscribe, :parent => RelationshipMetaparam, :attributes => {:direction => :in, :events => :ALL_EVENTS, :callback => :refresh}) do
desc "References to one or more objects that this object depends on. This
metaparameter creates a dependency relationship like **require,**
and also causes the dependent object to be refreshed when the
subscribed object is changed. For instance:
class nagios {
file { 'nagconf':
path => \"/etc/nagios/nagios.conf\"
source => \"puppet://server/module/nagios.conf\",
}
service { 'nagios':
ensure => running,
subscribe => File['nagconf']
}
}
Currently the `exec`, `mount` and `service` types support
refreshing.
"
end
newmetaparam(:before, :parent => RelationshipMetaparam, :attributes => {:direction => :out, :events => :NONE}) do
desc %{References to one or more objects that depend on this object. 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.}
end
newmetaparam(:notify, :parent => RelationshipMetaparam, :attributes => {:direction => :out, :events => :ALL_EVENTS, :callback => :refresh}) do
desc %{References to one or more objects that depend on this object. This
parameter is the opposite of **subscribe** --- it creates a
dependency relationship like **before,** and also causes the
dependent object(s) to be refreshed when this object is changed. For
instance:
file { "/etc/sshd_config":
source => "....",
notify => Service['sshd']
}
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']: }
To order stages, use standard relationships:
stage { 'pre': before => Stage['main'] }
Or use the new relationship syntax:
Stage['pre'] -> Stage['main'] -> Stage['post']
Then use the new class parameters to specify a stage:
class { 'foo': stage => 'pre' }
Stages can only be set on classes, not individual resources. This will
fail:
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
return @defaultprovider if @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 }
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}"
)
end
@defaultprovider = defaults.shift unless defaults.empty?
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 unprovide(name)
Puppet.debug "Reloading #{name} #{self.name} provider"
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
# We're using a hacky way to get the name of our type, since there doesn't
# seem to be a correct way to introspect this at the time this code is run.
# We expect that the class in which this code is executed will be something
# like Puppet::Type::Ssh_authorized_key::ParameterProvider.
desc <<-EOT
The specific backend to use for this `#{self.to_s.split('::')[2].downcase}`
resource. You will seldom need to specify this --- Puppet will usually
discover the appropriate provider for your platform.
EOT
# 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
# Since we're mixing @doc with text from other sources, we must normalize
# its indentation with scrub. But we don't need to manually scrub the
# provider's doc string, since markdown_definitionlist sanitizes its inputs.
scrub(@doc) + "Available providers are:\n\n" + parenttype.providers.sort { |a,b|
a.to_s <=> b.to_s
}.collect { |i|
markdown_definitionlist( i, scrub(parenttype().provider(i).doc) )
}.join
end
defaultto {
prov = @resource.class.defaultprovider
prov.name if prov
}
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 @defaultprovider and @defaultprovider.name == name
@defaultprovider = nil
end
rmclass(name, :hash => provider_hash, :prefix => "Provider")
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 suitable?
# If we don't use providers, then we consider it suitable.
return true unless self.class.paramclass(:provider)
# We have a provider and it is suitable.
return true if provider && provider.class.suitable?
# We're using the default provider and there is one.
if !provider and self.class.defaultprovider
self.provider = self.class.defaultprovider.name
return true
end
# We specified an unsuitable provider, or there isn't any suitable
# provider.
false
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|
# 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
# memoizing this is worthwhile ~ 3 percent of calls are the "first time
# around" in an average run of Puppet. --daniel 2012-07-17
@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
def appliable_to_device?
self.class.can_apply_to(:device)
end
def appliable_to_host?
self.class.can_apply_to(:host)
end
end
end
require 'puppet/provider'
# Always load these types.
Puppet::Type.type(:component)
diff --git a/lib/puppet/type/file.rb b/lib/puppet/type/file.rb
index 716361586..65e996fb5 100644
--- a/lib/puppet/type/file.rb
+++ b/lib/puppet/type/file.rb
@@ -1,839 +1,837 @@
require 'digest/md5'
require 'cgi'
require 'etc'
require 'uri'
require 'fileutils'
require 'enumerator'
require 'pathname'
require 'puppet/network/handler'
require 'puppet/util/diff'
require 'puppet/util/checksums'
require 'puppet/util/backups'
require 'puppet/util/symbolic_file_mode'
Puppet::Type.newtype(:file) do
include Puppet::Util::MethodHelper
include Puppet::Util::Checksums
include Puppet::Util::Backups
include Puppet::Util::SymbolicFileMode
@doc = "Manages files, including their content, ownership, and permissions.
The `file` type can manage normal files, directories, and symlinks; the
type should be specified in the `ensure` attribute. Note that symlinks cannot
be managed on Windows systems.
File contents can be managed directly with the `content` attribute, or
downloaded from a remote source using the `source` attribute; the latter
can also be used to recursively serve directories (when the `recurse`
attribute is set to `true` or `local`). On Windows, note that file
contents are managed in binary mode; Puppet never automatically translates
line endings.
**Autorequires:** If Puppet is managing the user or group that owns a
file, the file resource will autorequire them. If Puppet is managing any
parent directories of a file, the file resource will autorequire them."
def self.title_patterns
- [ [ /^(.*?)\/*\Z/m, [ [ :path, lambda{|x| x} ] ] ] ]
+ [ [ /^(.*?)\/*\Z/m, [ [ :path ] ] ] ]
end
newparam(:path) do
desc <<-'EOT'
The path to the file to manage. Must be fully qualified.
On Windows, the path should include the drive letter and should use `/` as
the separator character (rather than `\\`).
EOT
isnamevar
validate do |value|
unless Puppet::Util.absolute_path?(value)
fail Puppet::Error, "File paths must be fully qualified, not '#{value}'"
end
end
# convert the current path in an index into the collection and the last
# path name. The aim is to use less storage for all common paths in a hierarchy
munge do |value|
# We know the value is absolute, so expanding it will just standardize it.
path, name = ::File.split(::File.expand_path(value))
{ :index => Puppet::FileCollection.collection.index(path), :name => name }
end
# and the reverse
unmunge do |value|
- basedir = Puppet::FileCollection.collection.path(value[:index])
-
- ::File.join( basedir, value[:name] )
+ ::File.join(Puppet::FileCollection.collection.path(value[:index]), value[:name])
end
end
newparam(:backup) do
desc "Whether files should be backed up before
being replaced. The preferred method of backing files up is via
a `filebucket`, which stores files by their MD5 sums and allows
easy retrieval without littering directories with backups. You
can specify a local filebucket or a network-accessible
server-based filebucket by setting `backup => bucket-name`.
Alternatively, if you specify any value that begins with a `.`
(e.g., `.puppet-bak`), then Puppet will use copy the file in
the same directory with that value as the extension of the
backup. Setting `backup => false` disables all backups of the
file in question.
Puppet automatically creates a local filebucket named `puppet` and
defaults to backing up there. To use a server-based filebucket,
you must specify one in your configuration.
filebucket { main:
server => puppet,
path => false,
# The path => false line works around a known issue with the filebucket type.
}
The `puppet master` daemon creates a filebucket by default,
so you can usually back up to your main server with this
configuration. Once you've described the bucket in your
configuration, you can use it in any file's backup attribute:
file { \"/my/file\":
source => \"/path/in/nfs/or/something\",
backup => main
}
This will back the file up to the central server.
At this point, the benefits of using a central filebucket are that you
do not have backup files lying around on each of your machines, a given
version of a file is only backed up once, you can restore any given file
manually (no matter how old), and you can use Puppet Dashboard to view
file contents. Eventually, transactional support will be able to
automatically restore filebucketed files.
"
defaultto "puppet"
munge do |value|
# I don't really know how this is happening.
value = value.shift if value.is_a?(Array)
case value
when false, "false", :false
false
when true, "true", ".puppet-bak", :true
".puppet-bak"
when String
value
else
self.fail "Invalid backup type #{value.inspect}"
end
end
end
newparam(:recurse) do
desc "Whether and how deeply to do recursive
management. Options are:
* `inf,true` --- Regular style recursion on both remote and local
directory structure.
* `remote` --- Descends recursively into the remote directory
but not the local directory. Allows copying of
a few files into a directory containing many
unmanaged files without scanning all the local files.
* `false` --- Default of no recursion.
* `[0-9]+` --- Same as true, but limit recursion. Warning: this syntax
has been deprecated in favor of the `recurselimit` attribute.
"
newvalues(:true, :false, :inf, :remote, /^[0-9]+$/)
# Replace the validation so that we allow numbers in
# addition to string representations of them.
validate { |arg| }
munge do |value|
newval = super(value)
case newval
when :true, :inf; true
when :false; false
when :remote; :remote
when Integer, Fixnum, Bignum
self.warning "Setting recursion depth with the recurse parameter is now deprecated, please use recurselimit"
# recurse == 0 means no recursion
return false if value == 0
resource[:recurselimit] = value
true
when /^\d+$/
self.warning "Setting recursion depth with the recurse parameter is now deprecated, please use recurselimit"
value = Integer(value)
# recurse == 0 means no recursion
return false if value == 0
resource[:recurselimit] = value
true
else
self.fail "Invalid recurse value #{value.inspect}"
end
end
end
newparam(:recurselimit) do
desc "How deeply to do recursive management."
newvalues(/^[0-9]+$/)
munge do |value|
newval = super(value)
case newval
when Integer, Fixnum, Bignum; value
when /^\d+$/; Integer(value)
else
self.fail "Invalid recurselimit value #{value.inspect}"
end
end
end
newparam(:replace, :boolean => true) do
desc "Whether to replace a file that already exists on the local system but
whose content doesn't match what the `source` or `content` attribute
specifies. Setting this to false allows file resources to initialize files
without overwriting future changes. Note that this only affects content;
Puppet will still manage ownership and permissions. Defaults to `true`."
newvalues(:true, :false)
aliasvalue(:yes, :true)
aliasvalue(:no, :false)
defaultto :true
end
newparam(:force, :boolean => true) do
desc "Perform the file operation even if it will destroy one or more directories.
You must use `force` in order to:
* `purge` subdirectories
* Replace directories with files or links
* Remove a directory when `ensure => absent`"
newvalues(:true, :false)
defaultto false
end
newparam(:ignore) do
desc "A parameter which omits action on files matching
specified patterns during recursion. Uses Ruby's builtin globbing
engine, so shell metacharacters are fully supported, e.g. `[a-z]*`.
Matches that would descend into the directory structure are ignored,
e.g., `*/*`."
validate do |value|
unless value.is_a?(Array) or value.is_a?(String) or value == false
self.devfail "Ignore must be a string or an Array"
end
end
end
newparam(:links) do
desc "How to handle links during file actions. During file copying,
`follow` will copy the target file instead of the link, `manage`
will copy the link itself, and `ignore` will just pass it by.
When not copying, `manage` and `ignore` behave equivalently
(because you cannot really ignore links entirely during local
recursion), and `follow` will manage the file to which the link points."
newvalues(:follow, :manage)
defaultto :manage
end
newparam(:purge, :boolean => true) do
desc "Whether unmanaged files should be purged. This option only makes
sense when managing directories with `recurse => true`.
* When recursively duplicating an entire directory with the `source`
attribute, `purge => true` will automatically purge any files
that are not in the source directory.
* When managing files in a directory as individual resources,
setting `purge => true` will purge any files that aren't being
specifically managed.
If you have a filebucket configured, the purged files will be uploaded,
but if you do not, this will destroy data."
defaultto :false
newvalues(:true, :false)
end
newparam(:sourceselect) do
desc "Whether to copy all valid sources, or just the first one. This parameter
only affects recursive directory copies; by default, the first valid
source is the only one used, but if this parameter is set to `all`, then
all valid sources will have all of their contents copied to the local
system. If a given file exists in more than one source, the version from
the earliest source in the list will be used."
defaultto :first
newvalues(:first, :all)
end
# Autorequire the nearest ancestor directory found in the catalog.
autorequire(:file) do
req = []
path = Pathname.new(self[:path])
if !path.root?
# Start at our parent, to avoid autorequiring ourself
parents = path.parent.enum_for(:ascend)
if found = parents.find { |p| catalog.resource(:file, p.to_s) }
req << found.to_s
end
end
# if the resource is a link, make sure the target is created first
req << self[:target] if self[:target]
req
end
# Autorequire the owner and group of the file.
{:user => :owner, :group => :group}.each do |type, property|
autorequire(type) do
if @parameters.include?(property)
# The user/group property automatically converts to IDs
next unless should = @parameters[property].shouldorig
val = should[0]
if val.is_a?(Integer) or val =~ /^\d+$/
nil
else
val
end
end
end
end
CREATORS = [:content, :source, :target]
SOURCE_ONLY_CHECKSUMS = [:none, :ctime, :mtime]
validate do
creator_count = 0
CREATORS.each do |param|
creator_count += 1 if self.should(param)
end
creator_count += 1 if @parameters.include?(:source)
self.fail "You cannot specify more than one of #{CREATORS.collect { |p| p.to_s}.join(", ")}" if creator_count > 1
self.fail "You cannot specify a remote recursion without a source" if !self[:source] and self[:recurse] == :remote
self.fail "You cannot specify source when using checksum 'none'" if self[:checksum] == :none && !self[:source].nil?
SOURCE_ONLY_CHECKSUMS.each do |checksum_type|
self.fail "You cannot specify content when using checksum '#{checksum_type}'" if self[:checksum] == checksum_type && !self[:content].nil?
end
self.warning "Possible error: recurselimit is set but not recurse, no recursion will happen" if !self[:recurse] and self[:recurselimit]
provider.validate if provider.respond_to?(:validate)
end
def self.[](path)
return nil unless path
super(path.gsub(/\/+/, '/').sub(/\/$/, ''))
end
def self.instances
return []
end
# Determine the user to write files as.
def asuser
if self.should(:owner) and ! self.should(:owner).is_a?(Symbol)
writeable = Puppet::Util::SUIDManager.asuser(self.should(:owner)) {
FileTest.writable?(::File.dirname(self[:path]))
}
# If the parent directory is writeable, then we execute
# as the user in question. Otherwise we'll rely on
# the 'owner' property to do things.
asuser = self.should(:owner) if writeable
end
asuser
end
def bucket
return @bucket if @bucket
backup = self[:backup]
return nil unless backup
return nil if backup =~ /^\./
unless catalog or backup == "puppet"
fail "Can not find filebucket for backups without a catalog"
end
unless catalog and filebucket = catalog.resource(:filebucket, backup) or backup == "puppet"
fail "Could not find filebucket #{backup} specified in backup"
end
return default_bucket unless filebucket
@bucket = filebucket.bucket
@bucket
end
def default_bucket
Puppet::Type.type(:filebucket).mkdefaultbucket.bucket
end
# Does the file currently exist? Just checks for whether
# we have a stat
def exist?
stat ? true : false
end
# We have to do some extra finishing, to retrieve our bucket if
# there is one.
def finish
# Look up our bucket, if there is one
bucket
super
end
# Create any children via recursion or whatever.
def eval_generate
return [] unless self.recurse?
recurse
#recurse.reject do |resource|
# catalog.resource(:file, resource[:path])
#end.each do |child|
# catalog.add_resource child
# catalog.relationship_graph.add_edge self, child
#end
end
def ancestors
ancestors = Pathname.new(self[:path]).enum_for(:ascend).map(&:to_s)
ancestors.delete(self[:path])
ancestors
end
def flush
# We want to make sure we retrieve metadata anew on each transaction.
@parameters.each do |name, param|
param.flush if param.respond_to?(:flush)
end
@stat = :needs_stat
end
def initialize(hash)
# Used for caching clients
@clients = {}
super
# If they've specified a source, we get our 'should' values
# from it.
unless self[:ensure]
if self[:target]
self[:ensure] = :link
elsif self[:content]
self[:ensure] = :file
end
end
@stat = :needs_stat
end
# Configure discovered resources to be purged.
def mark_children_for_purging(children)
children.each do |name, child|
next if child[:source]
child[:ensure] = :absent
end
end
# Create a new file or directory object as a child to the current
# object.
def newchild(path)
full_path = ::File.join(self[:path], path)
# Add some new values to our original arguments -- these are the ones
# set at initialization. We specifically want to exclude any param
# values set by the :source property or any default values.
# LAK:NOTE This is kind of silly, because the whole point here is that
# the values set at initialization should live as long as the resource
# but values set by default or by :source should only live for the transaction
# or so. Unfortunately, we don't have a straightforward way to manage
# the different lifetimes of this data, so we kludge it like this.
# The right-side hash wins in the merge.
options = @original_parameters.merge(:path => full_path).reject { |param, value| value.nil? }
# These should never be passed to our children.
[:parent, :ensure, :recurse, :recurselimit, :target, :alias, :source].each do |param|
options.delete(param) if options.include?(param)
end
self.class.new(options)
end
# Files handle paths specially, because they just lengthen their
# path names, rather than including the full parent's title each
# time.
def pathbuilder
# We specifically need to call the method here, so it looks
# up our parent in the catalog graph.
if parent = parent()
# We only need to behave specially when our parent is also
# a file
if parent.is_a?(self.class)
# Remove the parent file name
list = parent.pathbuilder
list.pop # remove the parent's path info
return list << self.ref
else
return super
end
else
return [self.ref]
end
end
# Should we be purging?
def purge?
@parameters.include?(:purge) and (self[:purge] == :true or self[:purge] == "true")
end
# Recursively generate a list of file resources, which will
# be used to copy remote files, manage local files, and/or make links
# to map to another directory.
def recurse
children = (self[:recurse] == :remote) ? {} : recurse_local
if self[:target]
recurse_link(children)
elsif self[:source]
recurse_remote(children)
end
# If we're purging resources, then delete any resource that isn't on the
# remote system.
mark_children_for_purging(children) if self.purge?
result = children.values.sort { |a, b| a[:path] <=> b[:path] }
remove_less_specific_files(result)
end
# This is to fix bug #2296, where two files recurse over the same
# set of files. It's a rare case, and when it does happen you're
# not likely to have many actual conflicts, which is good, because
# this is a pretty inefficient implementation.
def remove_less_specific_files(files)
mypath = self[:path].split(::File::Separator)
other_paths = catalog.vertices.
select { |r| r.is_a?(self.class) and r[:path] != self[:path] }.
collect { |r| r[:path].split(::File::Separator) }.
select { |p| p[0,mypath.length] == mypath }
return files if other_paths.empty?
files.reject { |file|
path = file[:path].split(::File::Separator)
other_paths.any? { |p| path[0,p.length] == p }
}
end
# A simple method for determining whether we should be recursing.
def recurse?
self[:recurse] == true or self[:recurse] == :remote
end
# Recurse the target of the link.
def recurse_link(children)
perform_recursion(self[:target]).each do |meta|
if meta.relative_path == "."
self[:ensure] = :directory
next
end
children[meta.relative_path] ||= newchild(meta.relative_path)
if meta.ftype == "directory"
children[meta.relative_path][:ensure] = :directory
else
children[meta.relative_path][:ensure] = :link
children[meta.relative_path][:target] = meta.full_path
end
end
children
end
# Recurse the file itself, returning a Metadata instance for every found file.
def recurse_local
result = perform_recursion(self[:path])
return {} unless result
result.inject({}) do |hash, meta|
next hash if meta.relative_path == "."
hash[meta.relative_path] = newchild(meta.relative_path)
hash
end
end
# Recurse against our remote file.
def recurse_remote(children)
sourceselect = self[:sourceselect]
total = self[:source].collect do |source|
next unless result = perform_recursion(source)
return if top = result.find { |r| r.relative_path == "." } and top.ftype != "directory"
result.each { |data| data.source = "#{source}/#{data.relative_path}" }
break result if result and ! result.empty? and sourceselect == :first
result
end.flatten
# This only happens if we have sourceselect == :all
unless sourceselect == :first
found = []
total.reject! do |data|
result = found.include?(data.relative_path)
found << data.relative_path unless found.include?(data.relative_path)
result
end
end
total.each do |meta|
if meta.relative_path == "."
parameter(:source).metadata = meta
next
end
children[meta.relative_path] ||= newchild(meta.relative_path)
children[meta.relative_path][:source] = meta.source
children[meta.relative_path][:checksum] = :md5 if meta.ftype == "file"
children[meta.relative_path].parameter(:source).metadata = meta
end
children
end
def perform_recursion(path)
Puppet::FileServing::Metadata.indirection.search(
path,
:links => self[:links],
:recurse => (self[:recurse] == :remote ? true : self[:recurse]),
:recurselimit => self[:recurselimit],
:ignore => self[:ignore],
:checksum_type => (self[:source] || self[:content]) ? self[:checksum] : :none
)
end
# Remove any existing data. This is only used when dealing with
# links or directories.
def remove_existing(should)
return unless s = stat
self.fail "Could not back up; will not replace" unless perform_backup
unless should.to_s == "link"
return if s.ftype.to_s == should.to_s
end
case s.ftype
when "directory"
if self[:force] == :true
debug "Removing existing directory for replacement with #{should}"
FileUtils.rmtree(self[:path])
else
notice "Not removing directory; use 'force' to override"
return
end
when "link", "file"
debug "Removing existing #{s.ftype} for replacement with #{should}"
::File.unlink(self[:path])
else
self.fail "Could not back up files of type #{s.ftype}"
end
@stat = :needs_stat
true
end
def retrieve
if source = parameter(:source)
source.copy_source_values
end
super
end
# Set the checksum, from another property. There are multiple
# properties that modify the contents of a file, and they need the
# ability to make sure that the checksum value is in sync.
def setchecksum(sum = nil)
if @parameters.include? :checksum
if sum
@parameters[:checksum].checksum = sum
else
# If they didn't pass in a sum, then tell checksum to
# figure it out.
currentvalue = @parameters[:checksum].retrieve
@parameters[:checksum].checksum = currentvalue
end
end
end
# Should this thing be a normal file? This is a relatively complex
# way of determining whether we're trying to create a normal file,
# and it's here so that the logic isn't visible in the content property.
def should_be_file?
return true if self[:ensure] == :file
# I.e., it's set to something like "directory"
return false if e = self[:ensure] and e != :present
# The user doesn't really care, apparently
if self[:ensure] == :present
return true unless s = stat
return(s.ftype == "file" ? true : false)
end
# If we've gotten here, then :ensure isn't set
return true if self[:content]
return true if stat and stat.ftype == "file"
false
end
# Stat our file. Depending on the value of the 'links' attribute, we
# use either 'stat' or 'lstat', and we expect the properties to use the
# resulting stat object accordingly (mostly by testing the 'ftype'
# value).
#
# We use the initial value :needs_stat to ensure we only stat the file once,
# but can also keep track of a failed stat (@stat == nil). This also allows
# us to re-stat on demand by setting @stat = :needs_stat.
def stat
return @stat unless @stat == :needs_stat
method = :stat
# Files are the only types that support links
if (self.class.name == :file and self[:links] != :follow) or self.class.name == :tidy
method = :lstat
end
@stat = begin
::File.send(method, self[:path])
rescue Errno::ENOENT => error
nil
rescue Errno::ENOTDIR => error
nil
rescue Errno::EACCES => error
warning "Could not stat; permission denied"
nil
end
end
# We have to hack this just a little bit, because otherwise we'll get
# an error when the target and the contents are created as properties on
# the far side.
def to_trans(retrieve = true)
obj = super
obj.delete(:target) if obj[:target] == :notlink
obj
end
# Write out the file. Requires the property name for logging.
# Write will be done by the content property, along with checksum computation
def write(property)
remove_existing(:file)
use_temporary_file = write_temporary_file?
if use_temporary_file
path = "#{self[:path]}.puppettmp_#{rand(10000)}"
path = "#{self[:path]}.puppettmp_#{rand(10000)}" while ::File.exists?(path) or ::File.symlink?(path)
else
path = self[:path]
end
mode = self.should(:mode) # might be nil
umask = mode ? 000 : 022
mode_int = mode ? symbolic_mode_to_int(mode, 0644) : nil
content_checksum = Puppet::Util.withumask(umask) { ::File.open(path, 'wb', mode_int ) { |f| write_content(f) } }
# And put our new file in place
if use_temporary_file # This is only not true when our file is empty.
begin
fail_if_checksum_is_wrong(path, content_checksum) if validate_checksum?
::File.rename(path, self[:path])
rescue => detail
fail "Could not rename temporary file #{path} to #{self[:path]}: #{detail}"
ensure
# Make sure the created file gets removed
::File.unlink(path) if FileTest.exists?(path)
end
end
# make sure all of the modes are actually correct
property_fix
end
private
# Should we validate the checksum of the file we're writing?
def validate_checksum?
self[:checksum] !~ /time/
end
# Make sure the file we wrote out is what we think it is.
def fail_if_checksum_is_wrong(path, content_checksum)
newsum = parameter(:checksum).sum_file(path)
return if [:absent, nil, content_checksum].include?(newsum)
self.fail "File written to disk did not match checksum; discarding changes (#{content_checksum} vs #{newsum})"
end
# write the current content. Note that if there is no content property
# simply opening the file with 'w' as done in write is enough to truncate
# or write an empty length file.
def write_content(file)
(content = property(:content)) && content.write(file)
end
private
def write_temporary_file?
# unfortunately we don't know the source file size before fetching it
# so let's assume the file won't be empty
(c = property(:content) and c.length) || (s = @parameters[:source] and 1)
end
# There are some cases where all of the work does not get done on
# file creation/modification, so we have to do some extra checking.
def property_fix
properties.each do |thing|
next unless [:mode, :owner, :group, :seluser, :selrole, :seltype, :selrange].include?(thing.name)
# Make sure we get a new stat objct
@stat = :needs_stat
currentvalue = thing.retrieve
thing.sync unless thing.safe_insync?(currentvalue)
end
end
end
# We put all of the properties in separate files, because there are so many
# of them. The order these are loaded is important, because it determines
# the order they are in the property lit.
require 'puppet/type/file/checksum'
require 'puppet/type/file/content' # can create the file
require 'puppet/type/file/source' # can create the file
require 'puppet/type/file/target' # creates a different type of file
require 'puppet/type/file/ensure' # can create the file
require 'puppet/type/file/owner'
require 'puppet/type/file/group'
require 'puppet/type/file/mode'
require 'puppet/type/file/type'
require 'puppet/type/file/selcontext' # SELinux file context
require 'puppet/type/file/ctime'
require 'puppet/type/file/mtime'