diff --git a/Rakefile b/Rakefile
index 0e2e62d3a..25f5a23fa 100644
--- a/Rakefile
+++ b/Rakefile
@@ -1,68 +1,68 @@
# Rakefile for Puppet
begin
require 'rake/reductive'
rescue LoadError
$stderr.puts "You must have the Reductive build library in your RUBYLIB."
exit(14)
end
TESTHOSTS = %w{rh3a fedora1 centos1 freebsd1 culain}
project = Rake::RedLabProject.new("puppet") do |p|
p.summary = "System Automation and Configuration Management Software"
p.description = "Puppet is a declarative language for expressing system
configuration, a client and server for distributing it, and a library
for realizing the configuration."
p.filelist = [
'install.rb',
'[A-Z]*',
'lib/**/*.rb',
'test/**/*.rb',
'bin/**/*',
'ext/**/*',
'examples/**/*',
'conf/**/*'
]
p.add_dependency('facter', '1.1.0')
- p.epmhosts = %w{culain}
+ #p.epmhosts = %w{culain}
p.sunpkghost = "sol10b"
p.rpmhost = "fedora1"
end
if project.has?(:gem)
# Make our gem task. This actually just fills out the spec.
project.mkgemtask do |task|
task.require_path = 'lib' # Use these for libraries.
task.bindir = "bin" # Use these for applications.
task.executables = ["puppet", "puppetd", "puppetmasterd", "puppetdoc",
"puppetca"]
task.default_executable = "puppet"
task.autorequire = 'puppet'
#### Documentation and testing.
task.has_rdoc = true
#s.extra_rdoc_files = rd.rdoc_files.reject { |fn| fn =~ /\.rb$/ }.to_a
task.rdoc_options <<
'--title' << 'Puppet - Configuration Management' <<
'--main' << 'README' <<
'--line-numbers'
task.test_file = "test/Rakefile"
end
end
if project.has?(:epm)
project.mkepmtask do |task|
task.bins = FileList.new("bin/puppet", "bin/puppetca")
task.sbins = FileList.new("bin/puppetmasterd", "bin/puppetd")
task.rubylibs = FileList.new('lib/**/*')
end
end
# $Id$
diff --git a/lib/puppet/metatype/attributes.rb b/lib/puppet/metatype/attributes.rb
index 764aba8ee..28cefec6a 100644
--- a/lib/puppet/metatype/attributes.rb
+++ b/lib/puppet/metatype/attributes.rb
@@ -1,718 +1,740 @@
require 'puppet'
require 'puppet/type'
class Puppet::Type
class << self
include Puppet::Util::ClassGen
attr_reader :states
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
# 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
# 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
# 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
# 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.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.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
+
+ # 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
+
+ # If this param handles relationships, store that information
+ end
# Is the parameter in question a meta-parameter?
def self.metaparam?(param)
param = symbolize(param)
@@metaparamhash.include?(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, &block)
+ def self.newmetaparam(name, options = {}, &block)
@@metaparams ||= []
@@metaparamhash ||= {}
name = symbolize(name)
param = genclass(name,
- :parent => Puppet::Parameter,
+ :parent => options[:parent] || Puppet::Parameter,
:prefix => "MetaParam",
:hash => @@metaparamhash,
:array => @@metaparams,
+ :attributes => options[:attributes],
&block
)
+
+ handle_param_options(name, options)
param.ismetaparameter
return param
end
# 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
# 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] ||= {}
+ options[:attributes][:element] = self
param = genclass(name,
:parent => options[:parent] || Puppet::Parameter,
- :attributes => { :element => self },
+ :attributes => options[:attributes],
:block => block,
:prefix => "Parameter",
:array => @parameters,
:hash => @paramhash
)
+
+ handle_param_options(name, options)
# 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
if options[:event]
s.event = options[:event]
end
# define_method(name) do
# @states[name].should
# end
#
# define_method(name.to_s + "=") do |value|
# newstate(name, :should => value)
# end
return s
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 state class associated with a name
def self.statebyname(name)
@validstates[name]
end
def self.validattr?(name)
name = symbolize(name)
return true if name == :name
@validattrs ||= {}
unless @validattrs.include?(name)
if self.validstate?(name) or self.validparameter?(name) or self.metaparam?(name)
@validattrs[name] = true
else
@validattrs[name] = false
end
end
@validattrs[name]
end
# does the name reflect a valid state?
def self.validstate?(name)
name = symbolize(name)
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
# 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
# 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
# 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
# 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
# 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 the existing states
def eachstate
# states() is a private method
states().each { |state|
yield state
}
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 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
# 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
# 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
# Convert our object to a hash. This just includes states.
def to_hash
rethash = {}
[@parameters, @metaparams, @states].each do |hash|
hash.each do |name, obj|
rethash[name] = obj.value
end
end
rethash
end
# Meta-parameter methods: These methods deal with the results
# of specifying metaparameters
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
end
# $Id$
diff --git a/lib/puppet/metatype/container.rb b/lib/puppet/metatype/container.rb
index d7c509699..364639fd5 100644
--- a/lib/puppet/metatype/container.rb
+++ b/lib/puppet/metatype/container.rb
@@ -1,93 +1,97 @@
class Puppet::Type
attr_accessor :children
# 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
+
+ def depthfirst?
+ self.class.depthfirst?
+ 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
self.class.delete(self)
@parent = nil
# Remove the reference to the provider.
if self.provider
@provider.clear
@provider = nil
end
end
end
# $Id$
diff --git a/lib/puppet/metatype/manager.rb b/lib/puppet/metatype/manager.rb
index d2749b87d..bad41570a 100644
--- a/lib/puppet/metatype/manager.rb
+++ b/lib/puppet/metatype/manager.rb
@@ -1,153 +1,157 @@
require 'puppet'
require 'puppet/util/classgen'
# Methods dealing with Type management. This module gets included into the
# Puppet::Type class, it's just split out here for clarity.
module Puppet::MetaType
module Manager
include Puppet::Util::ClassGen
# remove all type instances; this is mostly only useful for testing
def allclear
Puppet::Event::Subscription.clear
@types.each { |name, type|
type.clear
}
end
# iterate across all of the subclasses of Type
def 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
# Load all types. Only currently used for documentation.
def loadall
typeloader.loadall
end
# Do an on-demand plugin load
def loadplugin(name)
paths = Puppet[:pluginpath].split(":")
unless paths.include?(Puppet[:plugindest])
Puppet.notice "Adding plugin destination %s to plugin search path" %
Puppet[:plugindest]
Puppet[:pluginpath] += ":" + Puppet[:plugindest]
end
paths.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 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
# Remove an existing defined type. Largely used for testing.
def rmtype(name)
# Then create the class.
klass = rmclass(name,
:hash => @types
)
+
+ if respond_to?("new" + name.to_s)
+ metaclass.send(:remove_method, "new" + name.to_s)
+ end
end
# Return a Type instance by name.
def type(name)
@types ||= {}
name = symbolize(name)
if t = @types[name]
return t
else
if typeloader.load(name)
unless @types.include? name
Puppet.warning "Loaded puppet/type/#{name} but no class was created"
end
else
# If we can't load it from there, try loading it as a plugin.
loadplugin(name)
end
return @types[name]
end
end
# Create a loader for Puppet types.
def typeloader
unless defined? @typeloader
@typeloader = Puppet::Autoload.new(self,
"puppet/type", :wrap => false
)
end
@typeloader
end
end
end
# $Id$
diff --git a/lib/puppet/metatype/metaparams.rb b/lib/puppet/metatype/metaparams.rb
index f28103a87..df226b146 100644
--- a/lib/puppet/metatype/metaparams.rb
+++ b/lib/puppet/metatype/metaparams.rb
@@ -1,355 +1,402 @@
require 'puppet'
require 'puppet/type'
class Puppet::Type
# 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
# We've got four relationship metaparameters, so this method is used
# to reduce code duplication between them.
def store_relationship(param, values)
# We need to support values passed in as an array or as a
# resource reference.
result = []
# 'values' could be an array or a reference. If it's an array,
# it could be an array of references or an array of arrays.
if values.is_a?(Puppet::Type)
result << [values.class.name, values.title]
else
unless values.is_a?(Array)
devfail "Relationships must be resource references"
end
if values[0].is_a?(String) or values[0].is_a?(Symbol)
# we're a type/title array reference
values[0] = symbolize(values[0])
result << values
else
# we're an array of stuff
values.each do |value|
if value.is_a?(Puppet::Type)
result << [value.class.name, value.title]
elsif value.is_a?(Array)
value[0] = symbolize(value[0])
result << value
else
devfail "Invalid relationship %s" % value.inspect
end
end
end
end
if existing = self[param]
result = existing + result
end
result
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|
- @parent.store_relationship(:require, 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|
- @parent.store_relationship(:subscribe, requires)
- 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
+
+ class RelationshipMetaparam < Puppet::Parameter
+ class << self
+ attr_accessor :direction, :events, :callback, :subclasses
+ end
+
+ @subclasses = []
+
+ def self.inherited(sub)
+ @subclasses << sub
+ end
+
+ def munge(rels)
+ @parent.store_relationship(self.class.name, rels)
+ 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 |value|
+ # we just have a name and a type, and we need to convert it
+ # to an object...
+ tname, name = value
+ object = nil
+ unless type = Puppet::Type.type(tname)
+ self.fail "Could not find type %s" % tname.inspect
+ end
+ 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? See the builddepends
+ # method for further docs on this.
+ if self.class.direction == :in
+ source = object
+ target = @parent
+ else
+ source = @parent
+ 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 => self.class.events
+ }
+
+ if method = self.class.callback
+ subargs[:callback] = method
+ end
+ rel = Puppet::Relationship.new(source, target, subargs)
+ end
+ end
+ end
+
+ def self.relationship_params
+ RelationshipMetaparam.subclasses
+ 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, :parent => RelationshipMetaparam, :attributes => {:direction => :in, :events => :NONE}) do
+ desc "One or more objects that this object depends on.
+ This is used purely for guaranteeing that changes to required objects
+ happen before the dependent object. For instance:
+
+ # 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\"]
+ }
- newmetaparam(:notify) do
+ 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.
+ "
+ 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, :parent => RelationshipMetaparam, :attributes => {:direction => :in, :events => :ALL_EVENTS, :callback => :refresh}) do
+ desc "One or more objects that this object depends on. Changes in the
+ subscribed to objects result in the dependent objects being
+ refreshed (e.g., a service will get restarted). For instance:
+
+ class nagios {
+ file { \"/etc/nagios/nagios.conf\":
+ source => \"puppet://server/module/nagios.conf\",
+ alias => nagconf # just to make things easier for me
+ }
+ service { nagios:
+ running => true,
+ subscribe => file[nagconf]
+ }
+ }
+ "
+ end
+
+ newmetaparam(:notify, :parent => RelationshipMetaparam, :attributes => {:direction => :out, :events => :ALL_EVENTS, :callback => :refresh}) do
desc %{This parameter is the opposite of **subscribe** -- it sends events
to the specified object:
file { "/etc/sshd_config":
source => "....",
notify => service[sshd]
}
service { sshd:
ensure => running
}
This will restart the sshd service if the sshd config file changes.}
-
-
- munge do |notifies|
- @parent.store_relationship(:notify, notifies)
- end
end
- newmetaparam(:before) do
+ newmetaparam(:before, :parent => RelationshipMetaparam, :attributes => {:direction => :out, :events => :NONE}) do
desc %{This parameter is the opposite of **require** -- it guarantees
that the specified object is applied later than the specifying
object:
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.}
-
- munge do |notifies|
- @parent.store_relationship(:before, notifies)
- end
end
end # Puppet::Type
# $Id$
diff --git a/lib/puppet/metatype/relationships.rb b/lib/puppet/metatype/relationships.rb
index 5f2471460..467b6187b 100644
--- a/lib/puppet/metatype/relationships.rb
+++ b/lib/puppet/metatype/relationships.rb
@@ -1,159 +1,117 @@
class Puppet::Type
# 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
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)
# 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.
unless dep.is_a? Puppet::Type
# Skip autorequires that we aren't managing
unless dep = typeobj[dep]
next
end
end
debug "Autorequiring %s" % [dep.ref]
reqs << Puppet::Relationship[dep, self]
}
}
return reqs
end
- # Build the dependencies associated with an individual object. :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. Note that the direction of the relationship
- # doesn't actually mean anything until you start using events --
- # the same information is present regardless.
+ # Build the dependencies associated with an individual object.
def builddepends
# Handle the requires
- {:require => [:NONE, nil, :in],
- :subscribe => [:ALL_EVENTS, :refresh, :in],
- :notify => [:ALL_EVENTS, :refresh, :out],
- :before => [:NONE, nil, :out]}.collect do |type, args|
- if self[type]
- handledepends(self[type], *args)
- end
- end.flatten.reject { |r| r.nil? }
- end
-
- def handledepends(requires, event, method, direction)
- # 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.collect { |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? See the builddepends
- # method for further docs on this.
- if direction == :in
- 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
- }
-
- if method
- subargs[:callback] = method
+ self.class.relationship_params.collect do |klass|
+ if param = @metaparams[klass.name]
+ param.to_edges
end
- rel = Puppet::Relationship.new(source, target, subargs)
- }
+ end.flatten.reject { |r| r.nil? }
end
-
- # Unsubscribe from a given object, possibly with a specific event.
- def unsubscribe(object, event = nil)
- # First look through our own relationship params
- [:require, :subscribe].each do |param|
- if values = self[param]
- newvals = values.reject { |d|
- d == [object.class.name, object.title]
- }
- if newvals.length != values.length
- self.delete(param)
- self[param] = newvals
- end
+
+ # Does this resource have a relationship with the other? We have to
+ # check each object for both directions of relationship.
+ def requires?(other)
+ them = [other.class.name, other.title]
+ me = [self.class.name, self.title]
+ self.class.relationship_params.each do |param|
+ case param.direction
+ when :in: return true if v = self[param.name] and v.include?(them)
+ when :out: return true if v = other[param.name] and v.include?(me)
end
end
+ return false
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
+
+ # Unsubscribe from a given object, possibly with a specific event.
+ def unsubscribe(object, event = nil)
+ # First look through our own relationship params
+ [:require, :subscribe].each do |param|
+ if values = self[param]
+ newvals = values.reject { |d|
+ d == [object.class.name, object.title]
+ }
+ if newvals.length != values.length
+ self.delete(param)
+ self[param] = newvals
+ end
+ end
+ end
+ end
end
# $Id$
diff --git a/lib/puppet/transaction.rb b/lib/puppet/transaction.rb
index 5bd5afd2f..adb5eb134 100644
--- a/lib/puppet/transaction.rb
+++ b/lib/puppet/transaction.rb
@@ -1,561 +1,586 @@
# the class that actually walks our resource/state tree, collects the changes,
# and performs them
require 'puppet'
require 'puppet/statechange'
module Puppet
class Transaction
attr_accessor :component, :resources, :ignoreschedules, :ignoretags
attr_accessor :relgraph, :sorted_resources
attr_writer :tags
include Puppet::Util
Puppet.config.setdefaults(:transaction,
:tags => ["", "Tags to use to find resources. If this is set, then
only resources tagged with the specified tags will be applied.
Values must be comma-separated."]
)
# Add some additional times for reporting
def addtimes(hash)
hash.each do |name, num|
@timemetrics[name] = num
end
end
# Apply all changes for a resource, returning a list of the events
# generated.
def apply(resource)
- # First make sure there are no failed dependencies. To do this,
- # we check for failures in any of the vertexes above us. It's not
- # enough to check the immediate dependencies, which is why we use
- # a tree from the reversed graph.
- @relgraph.reversal.tree_from_vertex(resource, :dfs).keys.each do |dep|
- skip = false
- if fails = failed?(dep)
- resource.notice "Dependency %s[%s] has %s failures" %
- [dep.class.name, dep.name, @failures[dep]]
- skip = true
- end
-
- if skip
- resource.warning "Skipping because of failed dependencies"
- @resourcemetrics[:skipped] += 1
- return []
- end
- end
-
- # If the resource needs to generate new objects at eval time, do it now.
- eval_generate(resource)
-
begin
changes = resource.evaluate
rescue => detail
if Puppet[:trace]
puts detail.backtrace
end
resource.err "Failed to retrieve current state: %s" % detail
# Mark that it failed
@failures[resource] += 1
# And then return
return []
end
unless changes.is_a? Array
changes = [changes]
end
if changes.length > 0
@resourcemetrics[:out_of_sync] += 1
end
resourceevents = changes.collect { |change|
@changes << change
@count += 1
change.transaction = self
events = nil
begin
# use an array, so that changes can return more than one
# event if they want
events = [change.forward].flatten.reject { |e| e.nil? }
rescue => detail
if Puppet[:trace]
puts detail.backtrace
end
change.state.err "change from %s to %s failed: %s" %
[change.state.is_to_s, change.state.should_to_s, detail]
@failures[resource] += 1
next
# FIXME this should support using onerror to determine
# behaviour; or more likely, the client calling us
# should do so
end
# Mark that our change happened, so it can be reversed
# if we ever get to that point
unless events.nil? or (events.is_a?(Array) and events.empty?)
change.changed = true
@resourcemetrics[:applied] += 1
end
events
}.flatten.reject { |e| e.nil? }
unless changes.empty?
# Record when we last synced
resource.cache(:synced, Time.now)
# Flush, if appropriate
if resource.respond_to?(:flush)
resource.flush
end
end
resourceevents
end
# Find all of the changed resources.
def changed?
@changes.find_all { |change| change.changed }.collect { |change|
change.state.parent
}.uniq
end
# Do any necessary cleanup. Basically just removes any generated
# resources.
def cleanup
@generated.each do |resource|
resource.remove
end
end
# See if the resource generates new resources at evaluation time.
def eval_generate(resource)
if resource.respond_to?(:eval_generate)
if children = resource.eval_generate
+ depthfirst = resource.depthfirst?
dependents = @relgraph.adjacent(resource, :direction => :out, :type => :edges)
targets = @relgraph.adjacent(resource, :direction => :in, :type => :edges)
children.each do |gen_child|
- gen_child.info "generated"
- @relgraph.add_edge!(resource, gen_child)
+ if depthfirst
+ @relgraph.add_edge!(gen_child, resource)
+ else
+ @relgraph.add_edge!(resource, gen_child)
+ end
dependents.each do |edge|
@relgraph.add_edge!(gen_child, edge.target, edge.label)
end
targets.each do |edge|
@relgraph.add_edge!(edge.source, gen_child, edge.label)
end
- @sorted_resources.insert(@sorted_resources.index(resource) + 1, gen_child)
@generated << gen_child
end
+ return children
end
end
end
# Evaluate a single resource.
def eval_resource(resource)
events = []
- unless tagged?(resource)
- resource.debug "Not tagged with %s" % tags.join(", ")
- return events
- end
-
- unless scheduled?(resource)
- resource.debug "Not scheduled"
- return events
- end
-
- @resourcemetrics[:scheduled] += 1
+ if skip?(resource)
+ @resourcemetrics[:skipped] += 1
+ else
+ @resourcemetrics[:scheduled] += 1
+
+ # We need to generate first regardless, because the recursive
+ # actions sometimes change how the top resource is applied.
+ children = eval_generate(resource)
+
+ if resource.depthfirst? and children
+ children.each do |child|
+ events += eval_resource(child)
+ end
+ end
- # Perform the actual changes
- seconds = thinmark do
- events = apply(resource)
- end
+ # Perform the actual changes
+ seconds = thinmark do
+ events += apply(resource)
+ end
+
+ if ! resource.depthfirst? and children
+ children.each do |child|
+ events += eval_resource(child)
+ end
+ end
- # Keep track of how long we spend in each type of resource
- @timemetrics[resource.class.name] += seconds
+ # Keep track of how long we spend in each type of resource
+ @timemetrics[resource.class.name] += seconds
+ end
# Check to see if there are any events for this resource
if triggedevents = trigger(resource)
events += triggedevents
end
# Collect the targets of any subscriptions to those events
@relgraph.matching_edges(events).each do |edge|
@targets[edge.target] << edge
end
# And return the events for collection
events
end
# This method does all the actual work of running a transaction. It
# collects all of the changes, executes them, and responds to any
# necessary events.
def evaluate
@count = 0
# Start logging.
Puppet::Log.newdestination(@report)
prepare()
begin
allevents = @sorted_resources.collect { |resource|
eval_resource(resource)
}.flatten.reject { |e| e.nil? }
ensure
# And then close the transaction log.
Puppet::Log.close(@report)
end
cleanup()
Puppet.debug "Finishing transaction %s with %s changes" %
[self.object_id, @count]
allevents
end
# Determine whether a given resource has failed.
def failed?(obj)
if @failures[obj] > 0
return @failures[obj]
else
return false
end
end
+
+ # Does this resource have any failed dependencies?
+ def failed_dependencies?(resource)
+ # First make sure there are no failed dependencies. To do this,
+ # we check for failures in any of the vertexes above us. It's not
+ # enough to check the immediate dependencies, which is why we use
+ # a tree from the reversed graph.
+ skip = false
+ @relgraph.reversal.tree_from_vertex(resource, :dfs).keys.each do |dep|
+ if fails = failed?(dep)
+ resource.notice "Dependency %s[%s] has %s failures" %
+ [dep.class.name, dep.name, @failures[dep]]
+ skip = true
+ end
+ end
+
+ return skip
+ end
# Collect any dynamically generated resources.
def generate
list = @resources.vertices
# Store a list of all generated resources, so that we can clean them up
# after the transaction closes.
@generated = []
newlist = []
while ! list.empty?
list.each do |resource|
if resource.respond_to?(:generate)
made = resource.generate
next unless made
unless made.is_a?(Array)
made = [made]
end
made.uniq!
made.each do |res|
@resources.add_vertex!(res)
newlist << res
@generated << res
end
end
end
list.clear
list = newlist
newlist = []
end
end
# this should only be called by a Puppet::Type::Component resource now
# and it should only receive an array
def initialize(resources)
@resources = resources.to_graph
@resourcemetrics = {
:total => @resources.vertices.length,
:out_of_sync => 0, # The number of resources that had changes
:applied => 0, # The number of resources fixed
:skipped => 0, # The number of resources skipped
:restarted => 0, # The number of resources triggered
:failed_restarts => 0, # The number of resources that fail a trigger
:scheduled => 0 # The number of resources scheduled
}
# Metrics for distributing times across the different types.
@timemetrics = Hash.new(0)
# The number of resources that were triggered in this run
@triggered = Hash.new { |hash, key|
hash[key] = Hash.new(0)
}
# Targets of being triggered.
@targets = Hash.new do |hash, key|
hash[key] = []
end
# The changes we're performing
@changes = []
# The resources that have failed and the number of failures each. This
# is used for skipping resources because of failed dependencies.
@failures = Hash.new do |h, key|
h[key] = 0
end
@report = Report.new
end
# Prefetch any providers that support it. We don't support prefetching
# types, just providers.
def prefetch
@resources.collect { |obj|
if pro = obj.provider
pro.class
else
nil
end
}.reject { |o| o.nil? }.uniq.each do |klass|
# XXX We need to do something special here in case of failure.
if klass.respond_to?(:prefetch)
klass.prefetch
end
end
end
# Prepare to evaluate the elements in a transaction.
def prepare
prefetch()
# Now add any dynamically generated resources
generate()
# Create a relationship graph from our resource graph
@relgraph = relationship_graph
@sorted_resources = @relgraph.topsort
end
# Create a graph of all of the relationships in our resource graph.
def relationship_graph
graph = Puppet::PGraph.new
# First create the dependency graph
@resources.vertices.each do |vertex|
graph.add_vertex!(vertex)
vertex.builddepends.each do |edge|
graph.add_edge!(edge)
end
end
# Then splice in the container information
graph.splice!(@resources, Puppet::Type::Component)
# Lastly, add in any autorequires
graph.vertices.each do |vertex|
vertex.autorequire.each do |edge|
unless graph.edge?(edge)
graph.add_edge!(edge)
end
end
end
return graph
end
# Generate a transaction report.
def report
@resourcemetrics[:failed] = @failures.find_all do |name, num|
num > 0
end.length
# Get the total time spent
@timemetrics[:total] = @timemetrics.inject(0) do |total, vals|
total += vals[1]
total
end
# Unfortunately, RRD does not deal well with changing lists of values,
# so we have to pick a list of values and stick with it. In this case,
# that means we record the total time, the config time, and that's about
# it. We should probably send each type's time as a separate metric.
@timemetrics.dup.each do |name, value|
if Puppet::Type.type(name)
@timemetrics.delete(name)
end
end
# Add all of the metrics related to resource count and status
@report.newmetric(:resources, @resourcemetrics)
# Record the relative time spent in each resource.
@report.newmetric(:time, @timemetrics)
# Then all of the change-related metrics
@report.newmetric(:changes,
:total => @changes.length
)
@report.time = Time.now
return @report
end
# Roll all completed changes back.
def rollback
@targets.clear
@triggered.clear
allevents = @changes.reverse.collect { |change|
# skip changes that were never actually run
unless change.changed
Puppet.debug "%s was not changed" % change.to_s
next
end
begin
events = change.backward
rescue => detail
Puppet.err("%s rollback failed: %s" % [change,detail])
if Puppet[:trace]
puts detail.backtrace
end
next
# at this point, we would normally do error handling
# but i haven't decided what to do for that yet
# so just record that a sync failed for a given resource
#@@failures[change.state.parent] += 1
# this still could get hairy; what if file contents changed,
# but a chmod failed? how would i handle that error? dern
end
@relgraph.matching_edges(events).each do |edge|
@targets[edge.target] << edge
end
# Now check to see if there are any events for this child.
# Kind of hackish, since going backwards goes a change at a
# time, not a child at a time.
trigger(change.state.parent)
# And return the events for collection
events
}.flatten.reject { |e| e.nil? }
end
# Is the resource currently scheduled?
def scheduled?(resource)
self.ignoreschedules or resource.scheduled?
end
+ # Should this resource be skipped?
+ def skip?(resource)
+ skip = false
+ if ! tagged?(resource)
+ resource.debug "Not tagged with %s" % tags.join(", ")
+ elsif ! scheduled?(resource)
+ resource.debug "Not scheduled"
+ elsif failed_dependencies?(resource)
+ resource.warning "Skipping because of failed dependencies"
+ else
+ return false
+ end
+ return true
+ end
+
# The tags we should be checking.
def tags
# Allow the tags to be overridden
unless defined? @tags
@tags = Puppet[:tags]
end
unless defined? @processed_tags
if @tags.nil? or @tags == ""
@tags = []
else
@tags = [@tags] unless @tags.is_a? Array
@tags = @tags.collect do |tag|
tag.split(/\s*,\s*/)
end.flatten
end
@processed_tags = true
end
@tags
end
# Is this resource tagged appropriately?
def tagged?(resource)
self.ignoretags or tags.empty? or resource.tagged?(tags)
end
# Are there any edges that target this resource?
def targeted?(resource)
@targets[resource]
end
# Trigger any subscriptions to a child. This does an upwardly recursive
# search -- it triggers the passed resource, but also the resource's parent
# and so on up the tree.
def trigger(child)
obj = child
callbacks = Hash.new { |hash, key| hash[key] = [] }
sources = Hash.new { |hash, key| hash[key] = [] }
trigged = []
while obj
if @targets.include?(obj)
callbacks.clear
sources.clear
@targets[obj].each do |edge|
# Some edges don't have callbacks
next unless edge.callback
# Collect all of the subs for each callback
callbacks[edge.callback] << edge
# And collect the sources for logging
sources[edge.source] << edge.callback
end
sources.each do |source, callbacklist|
obj.debug "%s[%s] results in triggering %s" %
[source.class.name, source.name, callbacklist.join(", ")]
end
callbacks.each do |callback, subs|
message = "Triggering '%s' from %s dependencies" %
[callback, subs.length]
obj.notice message
# At this point, just log failures, don't try to react
# to them in any way.
begin
obj.send(callback)
@resourcemetrics[:restarted] += 1
rescue => detail
obj.err "Failed to call %s on %s: %s" %
[callback, obj, detail]
@resourcemetrics[:failed_restarts] += 1
if Puppet[:trace]
puts detail.backtrace
end
end
# And then add an event for it.
trigged << Puppet::Event.new(
:event => :triggered,
:transaction => self,
:source => obj,
:message => message
)
triggered(obj, callback)
end
end
obj = obj.parent
end
if trigged.empty?
return nil
else
return trigged
end
end
def triggered(resource, method)
@triggered[resource][method] += 1
end
def triggered?(resource, method)
@triggered[resource][method]
end
end
end
require 'puppet/transaction/report'
# $Id$
diff --git a/lib/puppet/type/component.rb b/lib/puppet/type/component.rb
index cadd586c8..f203179a8 100644
--- a/lib/puppet/type/component.rb
+++ b/lib/puppet/type/component.rb
@@ -1,177 +1,135 @@
# the object allowing us to build complex structures
# this thing contains everything else, including itself
require 'puppet'
require 'puppet/type'
require 'puppet/transaction'
require 'puppet/pgraph'
module Puppet
newtype(:component) do
include Enumerable
newparam(:name) do
desc "The name of the component. Generally optional."
isnamevar
end
newparam(:type) do
desc "The type that this component maps to. Generally some kind of
class from the language."
defaultto "component"
end
- # topo sort functions
- def self.sort(objects)
- list = []
- tmplist = {}
-
- objects.each { |obj|
- self.recurse(obj, tmplist, list)
- }
-
- return list.flatten
- end
-
- # FIXME this method assumes that dependencies themselves
- # are never components
- def self.recurse(obj, inlist, list)
- if inlist.include?(obj.object_id)
- return
- end
- inlist[obj.object_id] = true
- begin
- obj.eachdependency { |req|
- self.recurse(req, inlist, list)
- }
- rescue Puppet::Error => detail
- raise Puppet::Error, "%s: %s" % [obj.path, detail]
- end
-
- if obj.is_a? self
- obj.each { |child|
- self.recurse(child, inlist, list)
- }
- else
- list << obj
- end
- end
-
# Remove a child from the component.
def delete(child)
if @children.include?(child)
@children.delete(child)
return true
else
return false
end
end
# Return each child in turn.
def each
@children.each { |child| yield child }
end
# flatten all children, sort them, and evaluate them in order
# this is only called on one component over the whole system
# this also won't work with scheduling, but eh
def evaluate
self.finalize unless self.finalized?
transaction = Puppet::Transaction.new(self)
transaction.component = self
return transaction
end
# Do all of the polishing off, mostly doing autorequires and making
# dependencies. This will get run once on the top-level component,
# and it will do everything necessary.
def finalize
started = {}
finished = {}
# First do all of the finish work, which mostly involves
self.delve do |object|
# Make sure we don't get into loops
if started.has_key?(object)
debug "Already finished %s" % object.title
next
else
started[object] = true
end
unless finished.has_key?(object)
object.finish
finished[object] = true
end
end
@finalized = true
end
def finalized?
if defined? @finalized
return @finalized
else
return false
end
end
-
- # Return a flattened array containing all of the children
- # and all child components' children, sorted in order of dependencies.
- def flatten
- self.class.sort(@children).flatten
- end
# Initialize a new component
def initialize(args)
@children = []
super(args)
end
# We have a different way of setting the title
def title
unless defined? @title
if self[:type] == self[:name] or self[:name] =~ /--\d+$/
@title = self[:type]
else
@title = "%s[%s]" % [self[:type],self[:name]]
end
end
return @title
end
def refresh
@children.collect { |child|
if child.respond_to?(:refresh)
child.refresh
child.log "triggering %s" % :refresh
end
}
end
# Convert to a graph object with all of the container info.
def to_graph
graph = Puppet::PGraph.new
delver = proc do |obj|
obj.each do |child|
if child.is_a?(Puppet::Type)
graph.add_edge!(obj, child)
delver.call(child)
end
end
end
delver.call(self)
return graph
end
def to_s
return "component(%s)" % self.title
end
end
end
# $Id$
diff --git a/lib/puppet/type/pfile.rb b/lib/puppet/type/pfile.rb
index b3bbba3b9..24f961a62 100644
--- a/lib/puppet/type/pfile.rb
+++ b/lib/puppet/type/pfile.rb
@@ -1,1033 +1,1026 @@
require 'digest/md5'
require 'cgi'
require 'etc'
require 'uri'
require 'fileutils'
require 'puppet/type/state'
require 'puppet/server/fileserver'
module Puppet
newtype(:file) do
@doc = "Manages local files, including setting ownership and
permissions, creation of both files and directories, and
retrieving entire files from remote servers. As Puppet matures, it
expected that the ``file`` element will be used less and less to
manage content, and instead native elements will be used to do so.
If you find that you are often copying files in from a central
location, rather than using native elements, please contact
Reductive Labs and we can hopefully work with you to develop a
native element to support what you are doing."
newparam(:path) do
desc "The path to the file to manage. Must be fully qualified."
isnamevar
validate do |value|
unless value =~ /^#{File::SEPARATOR}/
raise Puppet::Error, "File paths must be fully qualified"
end
end
end
newparam(:backup) do
desc "Whether files should be backed up before
being replaced. If a filebucket is specified, files will be
backed up there; else, they will be backed up in the same directory
with a ``.puppet-bak`` extension,, and no backups
will be made if backup is ``false``.
To use filebuckets, you must first create a filebucket in your
configuration:
filebucket { main:
server => puppet
}
The ``puppetmasterd`` 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:
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 only benefits to doing so 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, and you can restore
any given file manually, no matter how old. Eventually,
transactional support will be able to automatically restore
filebucketed files.
"
attr_reader :bucket
defaultto ".puppet-bak"
munge do |value|
case value
when false, "false", :false:
false
when true, "true", ".puppet-bak", :true:
".puppet-bak"
when String:
# We can't depend on looking this up right now,
# we have to do it after all of the objects
# have been instantiated.
@bucket = value
value
else
self.fail "Invalid backup type %s" %
value.inspect
end
end
# Provide a straight-through hook for setting the bucket.
def bucket=(bucket)
@value = bucket
@bucket = bucket
end
end
newparam(:linkmaker) do
desc "An internal parameter used by the *symlink*
type to do recursive link creation."
end
newparam(:recurse) do
desc "Whether and how deeply to do recursive
management."
newvalues(:true, :false, :inf, /^[0-9]+$/)
munge do |value|
newval = super(value)
case newval
when :true, :inf: true
when :false: false
else
newval
end
end
end
- newparam(:replace) do
+ newparam(:replace, :boolean => true) do
desc "Whether or not to replace a file that is
sourced but exists. This is useful for using file sources
purely for initialization."
newvalues(:true, :false)
defaultto :true
end
- newparam(:force) do
+ newparam(:force, :boolean => true) do
desc "Force the file operation. Currently only used when replacing
directories with links."
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., ``*/*``."
defaultto false
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, :ignore)
# :ignore and :manage behave equivalently on local files,
# but don't copy remote links
defaultto :ignore
end
- newparam(:purge) do
+ newparam(:purge, :boolean => true) do
desc "Whether unmanaged files should be purged. If you have a filebucket
configured the purged files will be uploaded, but if you do not,
this will destroy data. Only use this option for generated
files unless you really know what you are doing. This option only
makes sense when recursively managing directories."
defaultto :false
newvalues(:true, :false)
end
# Autorequire any parent directories.
autorequire(:file) do
File.dirname(self[:path])
end
# Autorequire the owner and group of the file.
{:user => :owner, :group => :group}.each do |type, state|
autorequire(type) do
if @states.include?(state)
# The user/group states automatically converts to IDs
next unless should = @states[state].shouldorig
val = should[0]
if val.is_a?(Integer) or val =~ /^\d+$/
nil
else
val
end
end
end
end
validate do
if self[:content] and self[:source]
self.fail "You cannot specify both content and a source"
end
end
# List files, but only one level deep.
def self.list(base = "/")
unless FileTest.directory?(base)
return []
end
files = []
Dir.entries(base).reject { |e|
e == "." or e == ".."
}.each do |name|
path = File.join(base, name)
if obj = self[path]
obj[:check] = :all
files << obj
else
files << self.create(
:name => path, :check => :all
)
end
end
files
end
@depthfirst = false
def argument?(arg)
@arghash.include?(arg)
end
# Determine the user to write files as.
def asuser
if self.should(:owner) and ! self.should(:owner).is_a?(Symbol)
writeable = Puppet::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' state to do things.
if writeable
asuser = self.should(:owner)
end
end
return asuser
end
# We have to do some extra finishing, to retrieve our bucket if
# there is one
def finish
# Let's cache these values, since there should really only be
# a couple of these buckets
@@filebuckets ||= {}
# Look up our bucket, if there is one
if @parameters.include?(:backup) and bucket = @parameters[:backup].bucket
case bucket
when String:
if obj = @@filebuckets[bucket]
# This sets the @value on :backup, too
@parameters[:backup].bucket = obj
elsif obj = Puppet.type(:filebucket).bucket(bucket)
@@filebuckets[bucket] = obj
@parameters[:backup].bucket = obj
else
self.fail "Could not find filebucket %s" % bucket
end
when Puppet::Client::Dipper: # things are hunky-dorey
else
self.fail "Invalid bucket type %s" % bucket.class
end
end
super
end
# Create any children via recursion or whatever.
def eval_generate
recurse()
end
# Deal with backups.
def handlebackup(file = nil)
# let the path be specified
file ||= self[:path]
# if they specifically don't want a backup, then just say
# we're good
unless FileTest.exists?(file)
return true
end
unless self[:backup]
return true
end
case File.stat(file).ftype
when "directory":
if self[:recurse]
# we don't need to backup directories when recurse is on
return true
else
backup = self[:backup]
case backup
when Puppet::Client::Dipper:
notice "Recursively backing up to filebucket"
require 'find'
Find.find(self[:path]) do |f|
if File.file?(f)
sum = backup.backup(f)
self.info "Filebucketed %s to %s with sum %s" %
[f, backup.name, sum]
end
end
return true
when String:
newfile = file + backup
# Just move it, since it's a directory.
if FileTest.exists?(newfile)
remove_backup(newfile)
end
begin
bfile = file + backup
# Ruby 1.8.1 requires the 'preserve' addition, but
# later versions do not appear to require it.
FileUtils.cp_r(file, bfile, :preserve => true)
return true
rescue => detail
# since they said they want a backup, let's error out
# if we couldn't make one
self.fail "Could not back %s up: %s" %
[file, detail.message]
end
else
self.err "Invalid backup type %s" % backup.inspect
return false
end
end
when "file":
backup = self[:backup]
case backup
when Puppet::Client::Dipper:
sum = backup.backup(file)
self.info "Filebucketed to %s with sum %s" %
[backup.name, sum]
return true
when String:
newfile = file + backup
if FileTest.exists?(newfile)
remove_backup(newfile)
end
begin
# FIXME Shouldn't this just use a Puppet object with
# 'source' specified?
bfile = file + backup
# Ruby 1.8.1 requires the 'preserve' addition, but
# later versions do not appear to require it.
FileUtils.cp(file, bfile, :preserve => true)
return true
rescue => detail
# since they said they want a backup, let's error out
# if we couldn't make one
self.fail "Could not back %s up: %s" %
[file, detail.message]
end
else
self.err "Invalid backup type %s" % backup.inspect
return false
end
when "link": return true
else
self.notice "Cannot backup files of type %s" %
File.stat(file).ftype
return false
end
end
def handleignore(children)
return children unless self[:ignore]
self[:ignore].each { |ignore|
ignored = []
Dir.glob(File.join(self[:path],ignore), File::FNM_DOTMATCH) {
|match| ignored.push(File.basename(match))
}
children = children - ignored
}
return children
end
def initialize(hash)
# Store a copy of the arguments for later.
tmphash = hash.to_hash
# Used for caching clients
@clients = {}
super
# Get rid of any duplicate slashes, and remove any trailing slashes.
@title = @title.gsub(/\/+/, "/").sub(/\/$/, "")
# Clean out as many references to any file paths as possible.
# This was the source of many, many bugs.
@arghash = tmphash
@arghash.delete(self.class.namevar)
[:source, :parent].each do |param|
if @arghash.include?(param)
@arghash.delete(param)
end
end
- if @arghash[:target]
- warning "%s vs %s" % [@arghash[:ensure], @arghash[:target]]
- end
-
@stat = nil
end
# Build a recursive map of a link source
def linkrecurse(recurse)
target = @states[:target].should
method = :lstat
if self[:links] == :follow
method = :stat
end
targetstat = nil
unless FileTest.exist?(target)
return
end
# Now stat our target
targetstat = File.send(method, target)
unless targetstat.ftype == "directory"
return
end
# Now that we know our corresponding target is a directory,
# change our type
+ info "setting ensure to target"
self[:ensure] = :directory
unless FileTest.readable? target
self.notice "Cannot manage %s: permission denied" % self.name
return
end
children = Dir.entries(target).reject { |d| d =~ /^\.+$/ }
# Get rid of ignored children
if @parameters.include?(:ignore)
children = handleignore(children)
end
added = []
children.each do |file|
Dir.chdir(target) do
longname = File.join(target, file)
# Files know to create directories when recursion
# is enabled and we're making links
args = {
:recurse => recurse,
:ensure => longname
}
if child = self.newchild(file, true, args)
added << child
end
end
end
added
end
# Build up a recursive map of what's around right now
def localrecurse(recurse)
unless FileTest.exist?(self[:path]) and self.stat.directory?
#self.info "%s is not a directory; not recursing" %
# self[:path]
return
end
unless FileTest.readable? self[:path]
self.notice "Cannot manage %s: permission denied" % self.name
return
end
children = Dir.entries(self[:path])
#Get rid of ignored children
if @parameters.include?(:ignore)
children = handleignore(children)
end
added = []
children.each { |file|
file = File.basename(file)
next if file =~ /^\.\.?$/ # skip . and ..
options = {:recurse => recurse}
if child = self.newchild(file, true, options)
# Mark any unmanaged files for removal if purge is set.
# Use the array rather than [] because tidy uses this method, too.
if @parameters.include?(:purge) and self.purge?
info "purging %s" % child.ref
child[:ensure] = :absent
else
child[:require] = self
end
added << child
end
}
added
end
# Create a new file or directory object as a child to the current
# object.
def newchild(path, local, hash = {})
# make local copy of arguments
args = @arghash.dup
if path =~ %r{^#{File::SEPARATOR}}
self.devfail(
"Must pass relative paths to PFile#newchild()"
)
else
path = File.join(self[:path], path)
end
args[:path] = path
unless hash.include?(:recurse)
if args.include?(:recurse)
if args[:recurse].is_a?(Integer)
args[:recurse] -= 1 # reduce the level of recursion
end
end
end
hash.each { |key,value|
args[key] = value
}
child = nil
klass = nil
# We specifically look in @parameters here, because 'linkmaker' isn't
# a valid attribute for subclasses, so using 'self[:linkmaker]' throws
# an error.
if @parameters.include?(:linkmaker) and
args.include?(:source) and ! FileTest.directory?(args[:source])
klass = Puppet.type(:symlink)
# clean up the args a lot for links
old = args.dup
args = {
:ensure => old[:source],
:path => path
}
else
klass = self.class
end
# The child might already exist because 'localrecurse' runs
# before 'sourcerecurse'. I could push the override stuff into
# a separate method or something, but the work is the same other
# than this last bit, so it doesn't really make sense.
if child = klass[path]
unless child.parent.object_id == self.object_id
self.debug "Not managing more explicit file %s" %
path
return nil
end
# This is only necessary for sourcerecurse, because we might have
# created the object with different 'should' values than are
# set remotely.
unless local
args.each { |var,value|
next if var == :path
next if var == :name
# behave idempotently
unless child.should(var) == value
child[var] = value
end
}
end
return nil
else # create it anew
#notice "Creating new file with args %s" % args.inspect
args[:parent] = self
begin
child = klass.implicitcreate(args)
# implicit creation can return nil
if child.nil?
return nil
end
rescue Puppet::Error => detail
self.notice(
"Cannot manage: %s" %
[detail.message]
)
self.debug args.inspect
child = nil
rescue => detail
self.notice(
"Cannot manage: %s" %
[detail]
)
self.debug args.inspect
child = nil
end
end
return child
end
# Files handle paths specially, because they just lengthen their
# path names, rather than including the full parent's title each
# time.
def pathbuilder
if defined? @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
ppath = @parent.path.sub(/\/?file=.+/, '')
tmp = []
if ppath != "/" and ppath != ""
tmp << ppath
end
tmp << self.class.name.to_s + "=" + self.name
return tmp
else
return super
end
else
# The top-level name is always puppet[top], so we don't
# bother with that. And we don't add the hostname
# here, it gets added in the log server thingy.
if self.name == "puppet[top]"
return ["/"]
else
# We assume that if we don't have a parent that we
# should not cache the path
return [self.class.name.to_s + "=" + self.name]
end
end
end
# Should we be purging?
def purge?
@parameters.include?(:purge) and (self[:purge] == :true or self[:purge] == "true")
end
# Recurse into the directory. This basically just calls 'localrecurse'
# and maybe 'sourcerecurse', returning the collection of generated
# files.
def recurse
# are we at the end of the recursion?
unless self.recurse?
return
end
recurse = self[:recurse]
# we might have a string, rather than a number
if recurse.is_a?(String)
if recurse =~ /^[0-9]+$/
recurse = Integer(recurse)
else # anything else is infinite recursion
recurse = true
end
end
if recurse.is_a?(Integer)
recurse -= 1
end
children = []
# We want to do link-recursing before normal recursion so that all
# of the target stuff gets copied over correctly.
if @states.include? :target and ret = self.linkrecurse(recurse)
children += ret
end
if ret = self.localrecurse(recurse)
children += ret
end
if @states.include?(:source) and ret = self.sourcerecurse(recurse)
children += ret
end
children
end
# A simple method for determining whether we should be recursing.
def recurse?
return false unless @parameters.include?(:recurse)
val = @parameters[:recurse].value
if val and (val == true or val > 0)
return true
else
return false
end
end
# Remove the old backup.
def remove_backup(newfile)
if self.class.name == :file and self[:links] != :follow
method = :lstat
else
method = :stat
end
old = File.send(method, newfile).ftype
if old == "directory"
raise Puppet::Error,
"Will not remove directory backup %s; use a filebucket" %
newfile
end
info "Removing old backup of type %s" %
File.send(method, newfile).ftype
begin
File.unlink(newfile)
rescue => detail
if Puppet[:trace]
puts detail.backtrace
end
self.err "Could not remove old backup: %s" %
detail
return false
end
end
# Remove any existing data. This is only used when dealing with
# links or directories.
def remove_existing(should)
return unless s = stat(true)
unless handlebackup
self.fail "Could not back up; will not replace"
end
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 %s" %
should
FileUtils.rmtree(self[:path])
else
notice "Not replacing directory; use 'force' to override"
end
when "link", "file":
debug "Removing existing %s for replacement with %s" %
[s.ftype, should]
File.unlink(self[:path])
else
self.fail "Could not back up files of type %s" % s.ftype
end
end
# a wrapper method to make sure the file exists before doing anything
def retrieve
unless stat = self.stat(true)
self.debug "File does not exist"
@states.each { |name,state|
- # We've already retrieved the source, and we don't
- # want to overwrite whatever it did. This is a bit
- # of a hack, but oh well, source is definitely special.
- # next if name == :source
state.is = :absent
}
# If the file doesn't exist but we have a source, then call
# retrieve on that state
if @states.include?(:source)
@states[:source].retrieve
end
return
end
states().each { |state|
state.retrieve
}
end
# This recurses against the remote source and makes sure the local
# and remote structures match. It's run after 'localrecurse'. This
# method only does anything when its corresponding remote entry is
# a directory; in that case, this method creates file objects that
# correspond to any contained remote files.
def sourcerecurse(recurse)
# we'll set this manually as necessary
if @arghash.include?(:ensure)
@arghash.delete(:ensure)
end
r = false
if recurse
unless recurse == 0
r = 1
end
end
ignore = self[:ignore]
@states[:source].should.each do |source|
sourceobj, path = uri2obj(source)
# okay, we've got our source object; now we need to
# build up a local file structure to match the remote
# one
server = sourceobj.server
desc = server.list(path, self[:links], r, ignore)
if desc == ""
next
end
# Now create a new child for every file returned in the list.
return desc.split("\n").collect { |line|
file, type = line.split("\t")
next if file == "/" # skip the listing object
name = file.sub(/^\//, '')
args = {:source => source + file}
if type == file
args[:recurse] = nil
end
self.newchild(name, false, args)
}.reject {|c| c.nil? }.each do |f| f.info "sourced" end
end
return []
end
# Set the checksum, from another state. There are multiple states 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 @states.include? :checksum
if sum
@states[:checksum].checksum = sum
else
# If they didn't pass in a sum, then tell checksum to
# figure it out.
@states[:checksum].retrieve
@states[:checksum].checksum = @states[:checksum].is
end
end
end
# Stat our file. Depending on the value of the 'links' attribute, we use
# either 'stat' or 'lstat', and we expect the states to use the resulting
# stat object accordingly (mostly by testing the 'ftype' value).
def stat(refresh = false)
method = :stat
# Files are the only types that support links
if self.class.name == :file and self[:links] != :follow
method = :lstat
end
path = self[:path]
# Just skip them when they don't exist at all.
unless FileTest.exists?(path) or FileTest.symlink?(path)
@stat = nil
return @stat
end
if @stat.nil? or refresh == true
begin
@stat = File.send(method, self[:path])
rescue Errno::ENOENT => error
@stat = nil
rescue Errno::EACCES => error
self.warning "Could not stat; permission denied"
@stat = nil
end
end
return @stat
end
def uri2obj(source)
sourceobj = FileSource.new
path = nil
unless source
devfail "Got a nil source"
end
if source =~ /^\//
source = "file://localhost/%s" % URI.escape(source)
sourceobj.mount = "localhost"
sourceobj.local = true
end
begin
uri = URI.parse(URI.escape(source))
rescue => detail
self.fail "Could not understand source %s: %s" %
[source, detail.to_s]
end
case uri.scheme
when "file":
unless defined? @@localfileserver
@@localfileserver = Puppet::Server::FileServer.new(
:Local => true,
:Mount => { "/" => "localhost" },
:Config => false
)
#@@localfileserver.mount("/", "localhost")
end
sourceobj.server = @@localfileserver
path = "/localhost" + uri.path
when "puppet":
args = { :Server => uri.host }
if uri.port
args[:Port] = uri.port
end
# FIXME We should cache a copy of this server
#sourceobj.server = Puppet::NetworkClient.new(args)
unless @clients.include?(source)
@clients[source] = Puppet::Client::FileClient.new(args)
end
sourceobj.server = @clients[source]
tmp = uri.path
if tmp =~ %r{^/(\w+)}
sourceobj.mount = $1
path = tmp
#path = tmp.sub(%r{^/\w+},'') || "/"
else
self.fail "Invalid source path %s" % tmp
end
else
self.fail "Got other recursive file proto %s from %s" %
[uri.scheme, source]
end
return [sourceobj, path.sub(/\/\//, '/')]
end
# Write out the file. We open the file correctly, with all of the
# uid and mode and such, and then yield the file handle for actual
# writing.
def write(usetmp = true)
mode = self.should(:mode)
remove_existing(:file)
# The temporary file
path = nil
if usetmp
path = self[:path] + ".puppettmp"
else
path = self[:path]
end
# As the correct user and group
Puppet::SUIDManager.asuser(asuser(), self.should(:group)) do
f = nil
# Open our file with the correct modes
if mode
Puppet::Util.withumask(000) do
f = File.open(path,
File::CREAT|File::WRONLY|File::TRUNC, mode)
end
else
f = File.open(path, File::CREAT|File::WRONLY|File::TRUNC)
end
# Yield it
yield f
f.flush
f.close
end
# And put our new file in place
if usetmp
begin
File.rename(path, self[:path])
rescue => detail
self.err "Could not rename tmp %s for replacing: %s" %
[self[:path], detail]
ensure
# Make sure the created file gets removed
if FileTest.exists?(path)
File.unlink(path)
end
end
end
# And then update our checksum, so the next run doesn't find it.
# FIXME This is extra work, because it's going to read the whole
# file back in again.
self.setchecksum
end
end # Puppet.type(:pfile)
# the filesource class can't include the path, because the path
# changes for every file instance
class FileSource
attr_accessor :mount, :root, :server, :local
end
# We put all of the states 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 state list.
require 'puppet/type/pfile/checksum'
require 'puppet/type/pfile/content' # can create the file
require 'puppet/type/pfile/source' # can create the file
require 'puppet/type/pfile/target'
require 'puppet/type/pfile/ensure' # can create the file
require 'puppet/type/pfile/uid'
require 'puppet/type/pfile/group'
require 'puppet/type/pfile/mode'
require 'puppet/type/pfile/type'
end
# $Id$
diff --git a/lib/puppet/type/pfile/checksum.rb b/lib/puppet/type/pfile/checksum.rb
index a91f7e017..c4ae6e8c3 100755
--- a/lib/puppet/type/pfile/checksum.rb
+++ b/lib/puppet/type/pfile/checksum.rb
@@ -1,341 +1,341 @@
# Keep a copy of the file checksums, and notify when they change.
# This state never actually modifies the system, it only notices when the system
# changes on its own.
module Puppet
Puppet.type(:file).newstate(:checksum) do
desc "How to check whether a file has changed. This state is used internally
for file copying, but it can also be used to monitor files somewhat
like Tripwire without managing the file contents in any way. You can
specify that a file's checksum should be monitored and then subscribe to
the file from another object and receive events to signify
checksum changes, for instance."
@event = :file_changed
@unmanaged = true
@validtypes = %w{md5 md5lite timestamp mtime time}
def self.validtype?(type)
@validtypes.include?(type)
end
@validtypes.each do |ctype|
newvalue(ctype) do
handlesum()
end
end
str = @validtypes.join("|")
# This is here because Puppet sets this internally, using
# {md5}......
newvalue(/^\{#{str}\}/) do
handlesum()
end
newvalue(:nosum) do
# nothing
:nochange
end
# If they pass us a sum type, behave normally, but if they pass
# us a sum type + sum, stick the sum in the cache.
munge do |value|
if value =~ /^\{(\w+)\}(.+)$/
type = symbolize($1)
sum = $2
cache(type, sum)
return type
else
if FileTest.directory?(@parent[:path])
return :time
else
return symbolize(value)
end
end
end
# Store the checksum in the data cache, or retrieve it if only the
# sum type is provided.
def cache(type, sum = nil)
unless type
raise ArgumentError, "A type must be specified to cache a checksum"
end
type = symbolize(type)
unless state = @parent.cached(:checksums)
self.debug "Initializing checksum hash"
state = {}
@parent.cache(:checksums, state)
end
if sum
unless sum =~ /\{\w+\}/
sum = "{%s}%s" % [type, sum]
end
state[type] = sum
else
return state[type]
end
end
# Because source and content and whomever else need to set the checksum
# and do the updating, we provide a simple mechanism for doing so.
def checksum=(value)
@is = value
munge(@should)
self.updatesum
end
def checktype
self.should || :md5
end
# Checksums need to invert how changes are printed.
def change_to_s
begin
if @is == :absent
return "defined '%s' as '%s'" %
[self.name, self.currentsum]
elsif self.should == :absent
return "undefined %s from '%s'" %
[self.name, self.is_to_s]
else
if defined? @cached and @cached
return "%s changed '%s' to '%s'" %
[self.name, @cached, self.is_to_s]
else
return "%s changed '%s' to '%s'" %
[self.name, self.currentsum, self.is_to_s]
end
end
rescue Puppet::Error, Puppet::DevError
raise
rescue => detail
raise Puppet::DevError, "Could not convert change %s to string: %s" %
[self.name, detail]
end
end
def currentsum
#"{%s}%s" % [self.should, cache(self.should)]
cache(checktype())
end
# Retrieve the cached sum
def getcachedsum
hash = nil
unless hash = @parent.cached(:checksums)
hash = {}
@parent.cache(:checksums, hash)
end
sumtype = self.should
if hash.include?(sumtype)
#self.notice "Found checksum %s for %s" %
# [hash[sumtype] ,@parent[:path]]
sum = hash[sumtype]
unless sum =~ /^\{\w+\}/
sum = "{%s}%s" % [sumtype, sum]
end
return sum
elsif hash.empty?
#self.notice "Could not find sum of type %s" % sumtype
return :nosum
else
#self.notice "Found checksum for %s but not of type %s" %
# [@parent[:path],sumtype]
return :nosum
end
end
# Calculate the sum from disk.
def getsum(checktype)
sum = ""
checktype = checktype.intern if checktype.is_a? String
case checktype
when :md5, :md5lite:
if ! FileTest.file?(@parent[:path])
@parent.debug "Cannot MD5 sum %s; using mtime" %
[@parent.stat.ftype]
sum = @parent.stat.mtime.to_s
else
begin
File.open(@parent[:path]) { |file|
text = nil
case checktype
when :md5
text = file.read
when :md5lite
text = file.read(512)
end
if text.nil?
self.debug "Not checksumming empty file %s" %
@parent[:path]
sum = 0
else
sum = Digest::MD5.hexdigest(text)
end
}
rescue Errno::EACCES => detail
self.notice "Cannot checksum %s: permission denied" %
@parent[:path]
@parent.delete(self.class.name)
rescue => detail
self.notice "Cannot checksum: %s" %
detail
@parent.delete(self.class.name)
end
end
when :timestamp, :mtime:
sum = @parent.stat.mtime.to_s
#sum = File.stat(@parent[:path]).mtime.to_s
when :time:
sum = @parent.stat.ctime.to_s
#sum = File.stat(@parent[:path]).ctime.to_s
else
raise Puppet::Error, "Invalid sum type %s" % checktype
end
return "{#{checktype}}" + sum.to_s
end
# At this point, we don't actually modify the system, we modify
# the stored state to reflect the current state, and then kick
# off an event to mark any changes.
def handlesum
if @is.nil?
raise Puppet::Error, "Checksum state for %s is somehow nil" %
@parent.title
end
if @is == :absent
self.retrieve
if self.insync?
self.debug "Checksum is already in sync"
return nil
end
#@parent.debug "%s(%s): after refresh, is '%s'" %
# [self.class.name,@parent.name,@is]
# If we still can't retrieve a checksum, it means that
# the file still doesn't exist
if @is == :absent
# if they're copying, then we won't worry about the file
# not existing yet
unless @parent.state(:source)
self.warning(
"File %s does not exist -- cannot checksum" %
@parent[:path]
)
end
return nil
end
end
# If the sums are different, then return an event.
if self.updatesum
return :file_changed
else
return nil
end
end
def insync?
@should = [checktype]
if cache(checktype())
return @is == currentsum()
else
# If there's no cached sum, then we don't want to generate
# an event.
return true
end
end
# Even though they can specify multiple checksums, the insync?
# mechanism can really only test against one, so we'll just retrieve
# the first specified sum type.
def retrieve(usecache = false)
# When the 'source' is retrieving, it passes "true" here so
# that we aren't reading the file twice in quick succession, yo.
if usecache and @is
return @is
end
stat = nil
unless stat = @parent.stat
self.is = :absent
return
end
if stat.ftype == "link" and @parent[:links] != :follow
self.debug "Not checksumming symlink"
#@parent.delete(:checksum)
self.is = self.currentsum
return
end
# Just use the first allowed check type
@is = getsum(checktype())
# If there is no sum defined, then store the current value
# into the cache, so that we're not marked as being
# out of sync. We don't want to generate an event the first
# time we get a sum.
unless cache(checktype())
# FIXME we should support an updatechecksums-like mechanism
self.updatesum
end
#@parent.debug "checksum state is %s" % self.is
end
# Store the new sum to the state db.
def updatesum
result = false
if @is.is_a?(Symbol)
raise Puppet::Error, "%s has invalid checksum" % @parent.title
end
# if we're replacing, vs. updating
if sum = cache(checktype())
- unless defined? @should
- raise Puppet::Error.new(
- ("@should is not initialized for %s, even though we " +
- "found a checksum") % @parent[:path]
- )
- end
+ # unless defined? @should
+ # raise Puppet::Error.new(
+ # ("@should is not initialized for %s, even though we " +
+ # "found a checksum") % @parent[:path]
+ # )
+ # end
if @is == sum
info "Sums are already equal"
return false
end
#if cache(self.should) == @is
# raise Puppet::Error, "Got told to update same sum twice"
#end
self.debug "Replacing %s checksum %s with %s" %
[@parent.title, sum, @is]
#@parent.debug "@is: %s; @should: %s" % [@is,@should]
result = true
else
@parent.debug "Creating checksum %s" % @is
result = false
end
# Cache the sum so the log message can be right if possible.
@cached = sum
cache(checktype(), @is)
return result
end
end
end
# $Id$
diff --git a/lib/puppet/type/pfile/ensure.rb b/lib/puppet/type/pfile/ensure.rb
index c998e0f7f..6f7b15d49 100755
--- a/lib/puppet/type/pfile/ensure.rb
+++ b/lib/puppet/type/pfile/ensure.rb
@@ -1,181 +1,180 @@
module Puppet
Puppet.type(:file).ensurable do
require 'etc'
desc "Whether to create files that don't currently exist.
Possible values are *absent*, *present* (equivalent to ``exists`` in
most file tests -- will match any form of file existence, and if the
file is missing will create an empty file), *file*, and
*directory*. Specifying ``absent`` will delete the file, although
currently this will not recursively delete directories.
Anything other than those values will be considered to be a symlink.
For instance, the following text creates a link:
# Useful on solaris
file { \"/etc/inetd.conf\":
ensure => \"/etc/inet/inetd.conf\"
}
You can make relative links:
# Useful on solaris
file { \"/etc/inetd.conf\":
ensure => \"inet/inetd.conf\"
}
If you need to make a relative link to a file named the same
as one of the valid values, you must prefix it with ``./`` or
something similar.
You can also make recursive symlinks, which will create a
directory structure that maps to the target directory,
with directories corresponding to each directory
and links corresponding to each file."
# Most 'ensure' states have a default, but with files we, um, don't.
nodefault
newvalue(:absent) do
File.unlink(@parent[:path])
end
aliasvalue(:false, :absent)
newvalue(:file) do
# Make sure we're not managing the content some other way
if state = @parent.state(:content) or state = @parent.state(:source)
state.sync
else
@parent.write(false) { |f| f.flush }
mode = @parent.should(:mode)
end
return :file_created
end
#aliasvalue(:present, :file)
newvalue(:present) do
# Make a file if they want something, but this will match almost
# anything.
set_file
end
newvalue(:directory) do
- p @is
mode = @parent.should(:mode)
parent = File.dirname(@parent[:path])
unless FileTest.exists? parent
raise Puppet::Error,
"Cannot create %s; parent directory %s does not exist" %
[@parent[:path], parent]
end
Puppet::SUIDManager.asuser(@parent.asuser()) {
if mode
Puppet::Util.withumask(000) do
Dir.mkdir(@parent[:path],mode)
end
else
Dir.mkdir(@parent[:path])
end
}
@parent.setchecksum
return :directory_created
end
newvalue(:link) do
if state = @parent.state(:target)
state.retrieve
if state.linkmaker
self.set_directory
return :directory_created
else
return state.mklink
end
else
self.fail "Cannot create a symlink without a target"
end
end
# Symlinks.
newvalue(/./) do
# This code never gets executed. We need the regex to support
# specifying it, but the work is done in the 'symlink' code block.
end
munge do |value|
value = super(value)
return value if value.is_a? Symbol
@parent[:target] = value
return :link
end
# Check that we can actually create anything
def check
basedir = File.dirname(@parent[:path])
if ! FileTest.exists?(basedir)
raise Puppet::Error,
"Can not create %s; parent directory does not exist" %
@parent.title
elsif ! FileTest.directory?(basedir)
raise Puppet::Error,
"Can not create %s; %s is not a directory" %
[@parent.title, dirname]
end
end
# We have to treat :present specially, because it works with any
# type of file.
def insync?
if self.should == :present
if @is.nil? or @is == :absent
return false
else
return true
end
else
return super
end
end
def retrieve
if stat = @parent.stat(false)
@is = stat.ftype.intern
else
if self.should == :false
@is = :false
else
@is = :absent
end
end
end
def sync
unless self.should == :absent
@parent.remove_existing(self.should)
end
event = super
# There are some cases where all of the work does not get done on
# file creation, so we have to do some extra checking.
@parent.each do |thing|
next unless thing.is_a? Puppet::State
next if thing == self
thing.retrieve
unless thing.insync?
thing.sync
end
end
return event
end
end
end
# $Id$
diff --git a/lib/puppet/type/pfile/source.rb b/lib/puppet/type/pfile/source.rb
index 8ac60422c..77f90bc9f 100755
--- a/lib/puppet/type/pfile/source.rb
+++ b/lib/puppet/type/pfile/source.rb
@@ -1,253 +1,261 @@
require 'puppet/server/fileserver'
module Puppet
# Copy files from a local or remote source. This state *only* does any work
# when the remote file is an actual file; in that case, this state copies
# the file down. If the remote file is a dir or a link or whatever, then
# this state, during retrieval, modifies the appropriate other states
# so that things get taken care of appropriately.
Puppet.type(:file).newstate(:source) do
PINPARAMS = Puppet::Server::FileServer::CHECKPARAMS
attr_accessor :source, :local
desc "Copy a file over the current file. Uses ``checksum`` to
determine when a file should be copied. Valid values are either
fully qualified paths to files, or URIs. Currently supported URI
types are *puppet* and *file*.
This is one of the primary mechanisms for getting content into
applications that Puppet does not directly support and is very
useful for those configuration files that don't change much across
sytems. For instance:
class sendmail {
file { \"/etc/mail/sendmail.cf\":
source => \"puppet://server/module/sendmail.cf\"
}
}
See the [fileserver docs][] for information on how to configure
and use file services within Puppet.
If you specify multiple file sources for a file, then the first
source that exists will be used. This allows you to specify
what amount to search paths for files:
file { \"/path/to/my/file\":
source => [
\"/nfs/files/file.$host\",
\"/nfs/files/file.$operatingsystem\",
\"/nfs/files/file\"
]
}
This will use the first found file as the source.
You cannot currently copy links using this mechanism; set ``links``
to ``follow`` if any remote sources are links.
[fileserver docs]: ../installing/fsconfigref.html
"
uncheckable
validate do |source|
unless @parent.uri2obj(source)
raise Puppet::Error, "Invalid source %s" % source
end
end
munge do |source|
# if source.is_a? Symbol
# return source
# end
# Remove any trailing slashes
source.sub(/\/$/, '')
end
+ def change_to_s
+ "replacing from source %s" % @source
+ end
+
def checksum
if defined?(@stats)
@stats[:checksum]
else
nil
end
end
# Ask the file server to describe our file.
def describe(source)
sourceobj, path = @parent.uri2obj(source)
server = sourceobj.server
begin
desc = server.describe(path, @parent[:links])
rescue NetworkClientError => detail
self.err "Could not describe %s: %s" %
[path, detail]
return nil
end
args = {}
PINPARAMS.zip(
desc.split("\t")
).each { |param, value|
if value =~ /^[0-9]+$/
value = value.to_i
end
unless value.nil?
args[param] = value
end
}
# we can't manage ownership as root, so don't even try
unless Puppet::SUIDManager.uid == 0
args.delete(:owner)
end
if args.empty? or (args[:type] == "link" and @parent[:links] == :ignore)
return nil
else
return args
end
end
# Have we successfully described the remote source?
def described?
! @stats.nil? and ! @stats[:type].nil? and @is != :notdescribed
end
# Use the info we get from describe() to check if we're in sync.
def insync?
unless described?
info "No specified sources exist"
return true
end
if @is == :nocopy
return true
end
# the only thing this actual state can do is copy files around. Therefore,
# only pay attention if the remote is a file.
unless @stats[:type] == "file"
return true
end
+
+ if @parent.is(:ensure) != :absent and ! @parent.replace?
+ return true
+ end
# Now, we just check to see if the checksums are the same
return @parent.is(:checksum) == @stats[:checksum]
end
# This basically calls describe() on our file, and then sets all
# of the local states appropriately. If the remote file is a normal
# file then we set it to copy; if it's a directory, then we just mark
# that the local directory should be created.
def retrieve(remote = true)
sum = nil
@source = nil
# This is set to false by the File#retrieve function on the second
# retrieve, so that we do not do two describes.
if remote
# Find the first source that exists. @shouldorig contains
# the sources as specified by the user.
@should.each { |source|
if @stats = self.describe(source)
@source = source
break
end
}
end
if @stats.nil? or @stats[:type].nil?
@is = :notdescribed
return nil
end
case @stats[:type]
when "directory", "file":
@parent[:ensure] = @stats[:type]
else
self.info @stats.inspect
self.err "Cannot use files of type %s as sources" %
@stats[:type]
@is = :nocopy
return
end
# Take each of the stats and set them as states on the local file
# if a value has not already been provided.
@stats.each { |stat, value|
next if stat == :checksum
next if stat == :type
# was the stat already specified, or should the value
# be inherited from the source?
unless @parent.argument?(stat)
@parent[stat] = value
+ @parent.state(stat).retrieve
end
}
@is = @stats[:checksum]
end
def should
@should
end
# Make sure we're also checking the checksum
def should=(value)
super
+
+ checks = (PINPARAMS + [:ensure])
+ checks.delete(:checksum)
- # @parent[:check] = [:checksum, :ensure]
+ @parent[:check] = checks
unless @parent.state(:checksum)
@parent[:checksum] = :md5
end
-
- unless @parent.state(:ensure)
- @parent[:check] = :ensure
- end
end
def sync
unless @stats[:type] == "file"
#if @stats[:type] == "directory"
#[@parent.name, @is.inspect, @should.inspect]
#end
raise Puppet::DevError, "Got told to copy non-file %s" %
@parent[:path]
end
sourceobj, path = @parent.uri2obj(@source)
begin
contents = sourceobj.server.retrieve(path, @parent[:links])
rescue NetworkClientError => detail
self.err "Could not retrieve %s: %s" %
[path, detail]
return nil
end
# FIXME It's stupid that this isn't taken care of in the
# protocol.
unless sourceobj.server.local
contents = CGI.unescape(contents)
end
if contents == ""
self.notice "Could not retrieve contents for %s" %
@source
end
exists = File.exists?(@parent[:path])
@parent.write { |f| f.print contents }
if exists
return :file_changed
else
return :file_created
end
end
end
end
# $Id$
diff --git a/lib/puppet/type/tidy.rb b/lib/puppet/type/tidy.rb
index 7e0a04353..cebbde74a 100755
--- a/lib/puppet/type/tidy.rb
+++ b/lib/puppet/type/tidy.rb
@@ -1,258 +1,266 @@
require 'etc'
require 'puppet/type/state'
require 'puppet/type/pfile'
module Puppet
newtype(:tidy, Puppet.type(:file)) do
@doc = "Remove unwanted files based on specific criteria. Multiple
criteria or OR'd together, so a file that is too large but is not
old enough will still get tidied."
newparam(:path) do
desc "The path to the file or directory to manage. Must be fully
qualified."
isnamevar
end
copyparam(Puppet.type(:file), :backup)
newparam(:age) do
desc "Tidy files whose age is equal to or greater than
the specified number of days. You can choose seconds, minutes,
hours, days, or weeks by specifying the first letter of any
of those words (e.g., '1w')."
@@ageconvertors = {
:s => 1,
:m => 60
}
@@ageconvertors[:h] = @@ageconvertors[:m] * 60
@@ageconvertors[:d] = @@ageconvertors[:h] * 24
@@ageconvertors[:w] = @@ageconvertors[:d] * 7
def convert(unit, multi)
if num = @@ageconvertors[unit]
return num * multi
else
self.fail "Invalid age unit '%s'" % unit
end
end
munge do |age|
unit = multi = nil
case age
when /^([0-9]+)(\w)\w*$/:
multi = Integer($1)
unit = $2.downcase.intern
when /^([0-9]+)$/:
multi = Integer($1)
unit = :d
else
self.fail "Invalid tidy age %s" % age
end
convert(unit, multi)
end
end
newparam(:size) do
desc "Tidy files whose size is equal to or greater than
the specified size. Unqualified values are in kilobytes, but
*b*, *k*, and *m* can be appended to specify *bytes*, *kilobytes*,
and *megabytes*, respectively. Only the first character is
significant, so the full word can also be used."
@@sizeconvertors = {
:b => 0,
:k => 1,
:m => 2,
:g => 3
}
def convert(unit, multi)
if num = @@sizeconvertors[unit]
result = multi
num.times do result *= 1024 end
return result
else
self.fail "Invalid size unit '%s'" % unit
end
end
munge do |size|
case size
when /^([0-9]+)(\w)\w*$/:
multi = Integer($1)
unit = $2.downcase.intern
when /^([0-9]+)$/:
multi = Integer($1)
unit = :k
else
self.fail "Invalid tidy size %s" % age
end
convert(unit, multi)
end
end
newparam(:type) do
desc "Set the mechanism for determining age."
newvalues(:atime, :mtime, :ctime)
defaultto :atime
end
newparam(:recurse) do
desc "If target is a directory, recursively descend
into the directory looking for files to tidy."
end
newparam(:rmdirs) do
desc "Tidy directories in addition to files; that is, remove
directories whose age is older than the specified criteria.
This will only remove empty directories, so all contained
files must also be tidied before a directory gets removed."
end
newstate(:tidyup) do
require 'etc'
@nodoc = true
@name = :tidyup
def age(stat)
type = nil
if stat.ftype == "directory"
type = :mtime
else
type = @parent[:type] || :atime
end
#return Integer(Time.now - stat.send(type))
return stat.send(type).to_i
end
def change_to_s
start = "Tidying"
unless insync_age?
start += ", older than %s seconds" % @parent[:age]
end
unless insync_size?
start += ", larger than %s bytes" % @parent[:size]
end
start
end
def insync_age?
if num = @parent[:age] and @is[0]
if (Time.now.to_i - @is[0]) > num
return false
end
end
true
end
def insync_size?
if num = @parent[:size] and @is[1]
if @is[1] > num
return false
end
end
true
end
def insync?
- insync_age? and insync_size?
+ if @is.is_a?(Symbol)
+ if @is == :absent
+ return true
+ else
+ return false
+ end
+ else
+ insync_age? and insync_size?
+ end
end
def retrieve
stat = nil
unless stat = @parent.stat
@is = :unknown
return
end
@is = [:age, :size].collect { |param|
if @parent[param]
self.send(param, stat)
end
}.reject { |p| p == false or p.nil? }
end
def size(stat)
return stat.size
end
def sync
file = @parent[:path]
case File.lstat(file).ftype
when "directory":
if @parent[:rmdirs]
subs = Dir.entries(@parent[:path]).reject { |d|
d == "." or d == ".."
}.length
if subs > 0
self.info "%s has %s children; not tidying" %
[@parent[:path], subs]
self.info Dir.entries(@parent[:path]).inspect
else
Dir.rmdir(@parent[:path])
end
else
self.debug "Not tidying directories"
return nil
end
when "file":
@parent.handlebackup(file)
File.unlink(file)
when "link": File.unlink(file)
else
self.fail "Cannot tidy files of type %s" %
File.lstat(file).ftype
end
return :file_tidied
end
end
# Erase PFile's validate method
validate do
end
def self.list
self.collect { |t| t }
end
@depthfirst = true
def initialize(hash)
super
#self.setdefaults
unless @parameters.include?(:age) or
@parameters.include?(:size)
unless FileTest.directory?(self[:path])
# don't do size comparisons for directories
self.fail "Tidy must specify size, age, or both"
end
end
# only allow backing up into filebuckets
unless self[:backup].is_a? Puppet::Client::Dipper
self[:backup] = false
end
self[:tidyup] = [:age, :size].collect { |param|
self[param]
}.reject { |p| p == false }
end
end
end
# $Id$
diff --git a/lib/puppet/util/posix.rb b/lib/puppet/util/posix.rb
index 75726b3da..01c3e25aa 100755
--- a/lib/puppet/util/posix.rb
+++ b/lib/puppet/util/posix.rb
@@ -1,78 +1,78 @@
# Utility methods for interacting with POSIX objects; mostly user and group
module Puppet::Util::POSIX
# Retrieve a field from a POSIX Etc object. The id can be either an integer
# or a name. This only works for users and groups.
def get_posix_field(space, field, id)
if id =~ /^\d+$/
id = Integer(id)
end
prefix = "get" + space.to_s
if id.is_a?(Integer)
method = (prefix + idfield(space).to_s).intern
else
method = (prefix + "nam").intern
end
-
+
begin
return Etc.send(method, id).send(field)
rescue ArgumentError => detail
# ignore it; we couldn't find the object
return nil
end
end
# Look in memory for an already-managed type and use its info if available.
def get_provider_value(type, field, id)
unless typeklass = Puppet::Type.type(type)
raise ArgumentError, "Invalid type %s" % type
end
id = id.to_s
chkfield = idfield(type)
obj = typeklass.find { |obj|
if id =~ /^\d+$/
obj.should(chkfield).to_s == id ||
obj.is(chkfield).to_s == id
else
obj[:name] == id
end
}
return nil unless obj
if obj.provider
begin
return obj.provider.send(field)
rescue => detail
if Puppet[:trace]
puts detail.backtrace
Puppet.err detail
return nil
end
end
end
end
# Determine what the field name is for users and groups.
def idfield(space)
case Puppet::Util.symbolize(space)
when :gr, :group: return :gid
when :pw, :user: return :uid
else
raise ArgumentError.new("Can only handle users and groups")
end
end
# Get the GID of a given group, provided either a GID or a name
def gid(group)
get_provider_value(:group, :gid, group) or get_posix_field(:gr, :gid, group)
end
# Get the UID of a given user, whether a UID or name is provided
def uid(user)
get_provider_value(:user, :uid, user) or get_posix_field(:pw, :uid, user)
end
end
# $Id$
\ No newline at end of file
diff --git a/test/other/relationships.rb b/test/other/relationships.rb
index e9c1d1c9c..164d52d2a 100755
--- a/test/other/relationships.rb
+++ b/test/other/relationships.rb
@@ -1,242 +1,266 @@
#!/usr/bin/env ruby
$:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/
require 'puppet'
require 'puppettest'
class TestRelationships < Test::Unit::TestCase
include PuppetTest
def newfile
assert_nothing_raised() {
return Puppet.type(:file).create(
:path => tempfile,
:check => [:mode, :owner, :group]
)
}
end
def check_relationship(sources, targets, out, refresher)
if out
deps = sources.builddepends
sources = [sources]
else
deps = targets.builddepends
targets = [targets]
end
assert_instance_of(Array, deps)
assert(! deps.empty?, "Did not receive any relationships")
deps.each do |edge|
assert_instance_of(Puppet::Relationship, edge)
end
sources.each do |source|
targets.each do |target|
edge = deps.find { |e| e.source == source and e.target == target }
assert(edge, "Could not find edge for %s => %s" %
[source.ref, target.ref])
if refresher
assert_equal(:ALL_EVENTS, edge.event)
assert_equal(:refresh, edge.callback)
else
assert_equal(:NONE, edge.event)
assert_nil(edge.callback, "Got a callback with no events")
end
end
end
end
# Make sure our various metaparams work correctly. We're just checking
# here whether they correctly set up the callbacks and the direction of
# the relationship.
def test_relationship_metaparams
out = {:require => false, :subscribe => false,
:notify => true, :before => true}
refreshers = [:subscribe, :notify]
[:require, :subscribe, :notify, :before].each do |param|
# Create three files to generate our events and three
# execs to receive them
files = []
execs = []
3.times do |i|
files << Puppet::Type.newfile(
:title => "file#{i}",
:path => tempfile(),
:ensure => :file
)
path = tempfile()
execs << Puppet::Type.newexec(
:title => "notifytest#{i}",
:path => "/usr/bin:/bin",
:command => "touch #{path}",
:refreshonly => true
)
end
# Add our first relationship
if out[param]
files[0][param] = execs[0]
sources = files[0]
targets = [execs[0]]
else
execs[0][param] = files[0]
sources = [files[0]]
targets = execs[0]
end
check_relationship(sources, targets, out[param], refreshers.include?(param))
# Now add another relationship
if out[param]
files[0][param] = execs[1]
targets << execs[1]
assert_equal(targets.collect { |t| [t.class.name, t.title]},
files[0][param], "Incorrect target list")
else
execs[0][param] = files[1]
sources << files[1]
assert_equal(sources.collect { |t| [t.class.name, t.title]},
execs[0][param], "Incorrect source list")
end
check_relationship(sources, targets, out[param], refreshers.include?(param))
Puppet::Type.allclear
end
end
def test_store_relationship
file = Puppet::Type.newfile :path => tempfile(), :mode => 0755
execs = []
3.times do |i|
execs << Puppet::Type.newexec(:title => "yay#{i}", :command => "/bin/echo yay")
end
# First try it with one object, specified as a reference and an array
result = nil
[execs[0], [:exec, "yay0"], ["exec", "yay0"]].each do |target|
assert_nothing_raised do
result = file.send(:store_relationship, :require, target)
end
assert_equal([[:exec, "yay0"]], result)
end
# Now try it with multiple objects
symbols = execs.collect { |e| [e.class.name, e.title] }
strings = execs.collect { |e| [e.class.name.to_s, e.title] }
[execs, symbols, strings].each do |target|
assert_nothing_raised do
result = file.send(:store_relationship, :require, target)
end
assert_equal(symbols, result)
end
# Make sure we can mix it up, even though this shouldn't happen
assert_nothing_raised do
result = file.send(:store_relationship, :require, [execs[0], [execs[1].class.name, execs[1].title]])
end
assert_equal([[:exec, "yay0"], [:exec, "yay1"]], result)
# Finally, make sure that new results get added to old. The only way
# to get rid of relationships is to delete the parameter.
file[:require] = execs[0]
assert_nothing_raised do
result = file.send(:store_relationship, :require, [execs[1], execs[2]])
end
assert_equal(symbols, result)
end
def test_newsub
file1 = newfile()
file2 = newfile()
sub = nil
assert_nothing_raised("Could not create subscription") {
sub = Puppet::Event::Subscription.new(
:source => file1,
:target => file2,
:event => :ALL_EVENTS,
:callback => :refresh
)
}
subs = nil
assert_nothing_raised {
subs = Puppet::Event::Subscription.subscribers(file1)
}
assert_equal(1, subs.length, "Got incorrect number of subs")
assert_equal(sub.target, subs[0], "Got incorrect sub")
deps = nil
assert_nothing_raised {
deps = Puppet::Event::Subscription.dependencies(file2)
}
assert_equal(1, deps.length, "Got incorrect number of deps")
assert_equal(sub, deps[0], "Got incorrect dep")
end
def test_eventmatch
file1 = newfile()
file2 = newfile()
sub = nil
assert_nothing_raised("Could not create subscription") {
sub = Puppet::Event::Subscription.new(
:source => file1,
:target => file2,
:event => :ALL_EVENTS,
:callback => :refresh
)
}
assert(sub.match?(:anything), "ALL_EVENTS did not match")
assert(! sub.match?(:NONE), "ALL_EVENTS matched :NONE")
sub.event = :file_created
assert(sub.match?(:file_created), "event did not match")
assert(sub.match?(:ALL_EVENTS), "ALL_EVENTS did not match")
assert(! sub.match?(:NONE), "ALL_EVENTS matched :NONE")
sub.event = :NONE
assert(! sub.match?(:file_created), "Invalid match")
assert(! sub.match?(:ALL_EVENTS), "ALL_EVENTS matched")
assert(! sub.match?(:NONE), "matched :NONE")
end
def test_autorequire
# We know that execs autorequire their cwd, so we'll use that
path = tempfile()
file = Puppet::Type.newfile(:title => "myfile", :path => path,
:ensure => :directory)
exec = Puppet::Type.newexec(:title => "myexec", :cwd => path,
:command => "/bin/echo")
reqs = nil
assert_nothing_raised do
reqs = exec.autorequire
end
assert_equal([Puppet::Relationship[file, exec]], reqs)
# Now make sure that these relationships are added to the transaction's
# relgraph
trans = Puppet::Transaction.new(newcomp(file, exec))
assert_nothing_raised do
trans.evaluate
end
graph = trans.relgraph
assert(graph.edge?(file, exec), "autorequire edge was not created")
end
+
+ def test_requires?
+ # Test the first direction
+ file1 = Puppet::Type.newfile(:title => "one", :path => tempfile,
+ :ensure => :directory)
+ file2 = Puppet::Type.newfile(:title => "two", :path => tempfile,
+ :ensure => :directory)
+
+ file1[:require] = file2
+ assert(file1.requires?(file2), "requires? failed to catch :require relationship")
+ file1.delete(:require)
+ assert(! file1.requires?(file2), "did not delete relationship")
+ file1[:subscribe] = file2
+ assert(file1.requires?(file2), "requires? failed to catch :subscribe relationship")
+ file1.delete(:subscribe)
+ assert(! file1.requires?(file2), "did not delete relationship")
+ file2[:before] = file1
+ assert(file1.requires?(file2), "requires? failed to catch :before relationship")
+ file2.delete(:before)
+ assert(! file1.requires?(file2), "did not delete relationship")
+ file2[:notify] = file1
+ assert(file1.requires?(file2), "requires? failed to catch :notify relationship")
+ end
+
end
# $Id$
diff --git a/test/other/transactions.rb b/test/other/transactions.rb
index 7342b57ec..9fc58526a 100755
--- a/test/other/transactions.rb
+++ b/test/other/transactions.rb
@@ -1,767 +1,766 @@
#!/usr/bin/env ruby
$:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/
require 'puppet'
require 'puppettest'
require 'puppettest/support/resources'
# $Id$
class TestTransactions < Test::Unit::TestCase
include PuppetTest::FileTesting
include PuppetTest::Support::Resources
def mkgenerator(&block)
# Create a bogus type that generates new instances with shorter
type = Puppet::Type.newtype(:generator) do
newparam(:name, :namevar => true)
end
if block
type.class_eval(&block)
end
cleanup do
Puppet::Type.rmtype(:generator)
end
return type
end
def test_reports
path1 = tempfile()
path2 = tempfile()
objects = []
objects << Puppet::Type.newfile(
:path => path1,
:content => "yayness"
)
objects << Puppet::Type.newfile(
:path => path2,
:content => "booness"
)
trans = assert_events([:file_created, :file_created], *objects)
report = nil
assert_nothing_raised {
report = trans.report
}
# First test the report logs
assert(report.logs.length > 0, "Did not get any report logs")
report.logs.each do |obj|
assert_instance_of(Puppet::Log, obj)
end
# Then test the metrics
metrics = report.metrics
assert(metrics, "Did not get any metrics")
assert(metrics.length > 0, "Did not get any metrics")
assert(metrics.has_key?("resources"), "Did not get object metrics")
assert(metrics.has_key?("changes"), "Did not get change metrics")
metrics.each do |name, metric|
assert_instance_of(Puppet::Metric, metric)
end
end
def test_prefetch
# Create a type just for testing prefetch
name = :prefetchtesting
$prefetched = false
type = Puppet::Type.newtype(name) do
newparam(:name) {}
end
cleanup do
Puppet::Type.rmtype(name)
end
# Now create a provider
type.provide(:prefetch) do
def self.prefetch
$prefetched = true
end
end
# Now create an instance
inst = type.create :name => "yay"
# Create a transaction
trans = Puppet::Transaction.new(newcomp(inst))
# Make sure prefetch works
assert_nothing_raised do
trans.prefetch
end
assert_equal(true, $prefetched, "type prefetch was not called")
# Now make sure it gets called from within evaluate()
$prefetched = false
assert_nothing_raised do
trans.evaluate
end
assert_equal(true, $prefetched, "evaluate did not call prefetch")
end
def test_refreshes_generate_events
path = tempfile()
firstpath = tempfile()
secondpath = tempfile()
file = Puppet::Type.newfile(:title => "file", :path => path, :content => "yayness")
first = Puppet::Type.newexec(:title => "first",
:command => "/bin/echo first > #{firstpath}",
:subscribe => [:file, path],
:refreshonly => true
)
second = Puppet::Type.newexec(:title => "second",
:command => "/bin/echo second > #{secondpath}",
:subscribe => [:exec, "first"],
:refreshonly => true
)
assert_apply(file, first, second)
assert(FileTest.exists?(secondpath), "Refresh did not generate an event")
end
unless %x{groups}.chomp.split(/ /).length > 1
$stderr.puts "You must be a member of more than one group to test transactions"
else
def ingroup(gid)
require 'etc'
begin
group = Etc.getgrgid(gid)
rescue => detail
puts "Could not retrieve info for group %s: %s" % [gid, detail]
return nil
end
return @groups.include?(group.name)
end
def setup
super
@groups = %x{groups}.chomp.split(/ /)
unless @groups.length > 1
p @groups
raise "You must be a member of more than one group to test this"
end
end
def newfile(hash = {})
tmpfile = tempfile()
File.open(tmpfile, "w") { |f| f.puts rand(100) }
# XXX now, because os x apparently somehow allows me to make a file
# owned by a group i'm not a member of, i have to verify that
# the file i just created is owned by one of my groups
# grrr
unless ingroup(File.stat(tmpfile).gid)
Puppet.info "Somehow created file in non-member group %s; fixing" %
File.stat(tmpfile).gid
require 'etc'
firstgr = @groups[0]
unless firstgr.is_a?(Integer)
str = Etc.getgrnam(firstgr)
firstgr = str.gid
end
File.chown(nil, firstgr, tmpfile)
end
hash[:name] = tmpfile
assert_nothing_raised() {
return Puppet.type(:file).create(hash)
}
end
def newservice
assert_nothing_raised() {
return Puppet.type(:service).create(
:name => "sleeper",
:type => "init",
:path => exampledir("root/etc/init.d"),
:hasstatus => true,
:check => [:ensure]
)
}
end
def newexec(file)
assert_nothing_raised() {
return Puppet.type(:exec).create(
:name => "touch %s" % file,
:path => "/bin:/usr/bin:/sbin:/usr/sbin",
:returns => 0
)
}
end
# modify a file and then roll the modifications back
def test_filerollback
transaction = nil
file = newfile()
states = {}
check = [:group,:mode]
file[:check] = check
assert_nothing_raised() {
file.retrieve
}
assert_nothing_raised() {
check.each { |state|
assert(file[state])
states[state] = file[state]
}
}
component = newcomp("file",file)
require 'etc'
groupname = Etc.getgrgid(File.stat(file.name).gid).name
assert_nothing_raised() {
# Find a group that it's not set to
group = @groups.find { |group| group != groupname }
unless group
raise "Could not find suitable group"
end
file[:group] = group
file[:mode] = "755"
}
trans = assert_events([:file_changed, :file_changed], component)
file.retrieve
assert_rollback_events(trans, [:file_changed, :file_changed], "file")
assert_nothing_raised() {
file.retrieve
}
states.each { |state,value|
assert_equal(
value,file.is(state), "File %s remained %s" % [state, file.is(state)]
)
}
end
# start a service, and then roll the modification back
# Disabled, because it wasn't really worth the effort.
def disabled_test_servicetrans
transaction = nil
service = newservice()
component = newcomp("service",service)
assert_nothing_raised() {
service[:ensure] = 1
}
service.retrieve
assert(service.insync?, "Service did not start")
system("ps -ef | grep ruby")
trans = assert_events([:service_started], component)
service.retrieve
assert_rollback_events(trans, [:service_stopped], "service")
end
# test that services are correctly restarted and that work is done
# in the right order
def test_refreshing
transaction = nil
file = newfile()
execfile = File.join(tmpdir(), "exectestingness")
exec = newexec(execfile)
states = {}
check = [:group,:mode]
file[:check] = check
file[:group] = @groups[0]
assert_apply(file)
@@tmpfiles << execfile
component = newcomp("both",file,exec)
# 'subscribe' expects an array of arrays
exec[:subscribe] = [[file.class.name,file.name]]
exec[:refreshonly] = true
assert_nothing_raised() {
file.retrieve
exec.retrieve
}
check.each { |state|
states[state] = file[state]
}
assert_nothing_raised() {
file[:mode] = "755"
}
trans = assert_events([:file_changed, :triggered], component)
assert(FileTest.exists?(execfile), "Execfile does not exist")
File.unlink(execfile)
assert_nothing_raised() {
file[:group] = @groups[1]
}
trans = assert_events([:file_changed, :triggered], component)
assert(FileTest.exists?(execfile), "Execfile does not exist")
end
# Verify that one component requiring another causes the contained
# resources in the requiring component to get refreshed.
def test_refresh_across_two_components
transaction = nil
file = newfile()
execfile = File.join(tmpdir(), "exectestingness2")
@@tmpfiles << execfile
exec = newexec(execfile)
states = {}
check = [:group,:mode]
file[:check] = check
file[:group] = @groups[0]
assert_apply(file)
fcomp = newcomp("file",file)
ecomp = newcomp("exec",exec)
component = newcomp("both",fcomp,ecomp)
# 'subscribe' expects an array of arrays
#component[:require] = [[file.class.name,file.name]]
ecomp[:subscribe] = fcomp
exec[:refreshonly] = true
trans = assert_events([], component)
assert_nothing_raised() {
file[:group] = @groups[1]
file[:mode] = "755"
}
trans = assert_events([:file_changed, :file_changed, :triggered], component)
end
# Make sure that multiple subscriptions get triggered.
def test_multisubs
path = tempfile()
file1 = tempfile()
file2 = tempfile()
file = Puppet.type(:file).create(
:path => path,
:ensure => "file"
)
exec1 = Puppet.type(:exec).create(
:path => ENV["PATH"],
:command => "touch %s" % file1,
:refreshonly => true,
:subscribe => [:file, path]
)
exec2 = Puppet.type(:exec).create(
:path => ENV["PATH"],
:command => "touch %s" % file2,
:refreshonly => true,
:subscribe => [:file, path]
)
assert_apply(file, exec1, exec2)
assert(FileTest.exists?(file1), "File 1 did not get created")
assert(FileTest.exists?(file2), "File 2 did not get created")
end
# Make sure that a failed trigger doesn't result in other events not
# getting triggered.
def test_failedrefreshes
path = tempfile()
newfile = tempfile()
file = Puppet.type(:file).create(
:path => path,
:ensure => "file"
)
svc = Puppet.type(:service).create(
:name => "thisservicedoesnotexist",
:subscribe => [:file, path]
)
exec = Puppet.type(:exec).create(
:path => ENV["PATH"],
:command => "touch %s" % newfile,
:logoutput => true,
:refreshonly => true,
:subscribe => [:file, path]
)
assert_apply(file, svc, exec)
assert(FileTest.exists?(path), "File did not get created")
assert(FileTest.exists?(newfile), "Refresh file did not get created")
end
# Make sure that unscheduled and untagged objects still respond to events
def test_unscheduled_and_untagged_response
Puppet::Type.type(:schedule).mkdefaultschedules
Puppet[:ignoreschedules] = false
file = Puppet.type(:file).create(
:name => tempfile(),
:ensure => "file"
)
fname = tempfile()
exec = Puppet.type(:exec).create(
:name => "touch %s" % fname,
:path => "/usr/bin:/bin",
:schedule => "monthly",
:subscribe => ["file", file.name]
)
comp = newcomp(file,exec)
comp.finalize
# Run it once
assert_apply(comp)
assert(FileTest.exists?(fname), "File did not get created")
assert(!exec.scheduled?, "Exec is somehow scheduled")
# Now remove it, so it can get created again
File.unlink(fname)
file[:content] = "some content"
assert_events([:file_changed, :triggered], comp)
assert(FileTest.exists?(fname), "File did not get recreated")
# Now remove it, so it can get created again
File.unlink(fname)
# And tag our exec
exec.tag("testrun")
# And our file, so it runs
file.tag("norun")
Puppet[:tags] = "norun"
file[:content] = "totally different content"
assert(! file.insync?, "Uh, file is in sync?")
assert_events([:file_changed, :triggered], comp)
assert(FileTest.exists?(fname), "File did not get recreated")
end
def test_failed_reqs_mean_no_run
exec = Puppet::Type.type(:exec).create(
:command => "/bin/mkdir /this/path/cannot/possibly/exit",
:title => "mkdir"
)
file1 = Puppet::Type.type(:file).create(
:title => "file1",
:path => tempfile(),
:require => exec,
:ensure => :file
)
file2 = Puppet::Type.type(:file).create(
:title => "file2",
:path => tempfile(),
:require => file1,
:ensure => :file
)
comp = newcomp(exec, file1, file2)
comp.finalize
assert_apply(comp)
assert(! FileTest.exists?(file1[:path]),
"File got created even tho its dependency failed")
assert(! FileTest.exists?(file2[:path]),
"File got created even tho its deep dependency failed")
end
end
def f(n)
Puppet::Type.type(:file)["/tmp/#{n.to_s}"]
end
def test_relationship_graph
one, two, middle, top = mktree
{one => two, "f" => "c", "h" => middle}.each do |source, target|
if source.is_a?(String)
source = f(source)
end
if target.is_a?(String)
target = f(target)
end
target[:require] = source
end
trans = Puppet::Transaction.new(top)
graph = nil
assert_nothing_raised do
graph = trans.relationship_graph
end
assert_instance_of(Puppet::PGraph, graph,
"Did not get relationship graph")
# Make sure all of the components are gone
comps = graph.vertices.find_all { |v| v.is_a?(Puppet::Type::Component)}
assert(comps.empty?, "Deps graph still contains components")
# It must be reversed because of how topsort works
sorted = graph.topsort.reverse
# Now make sure the appropriate edges are there and are in the right order
assert(graph.dependencies(f(:f)).include?(f(:c)),
"c not marked a dep of f")
assert(sorted.index(f(:c)) < sorted.index(f(:f)),
"c is not before f")
one.each do |o|
two.each do |t|
assert(graph.dependencies(o).include?(t),
"%s not marked a dep of %s" % [t.ref, o.ref])
assert(sorted.index(t) < sorted.index(o),
"%s is not before %s" % [t.ref, o.ref])
end
end
trans.resources.leaves(middle).each do |child|
assert(graph.dependencies(f(:h)).include?(child),
"%s not marked a dep of h" % [child.ref])
assert(sorted.index(child) < sorted.index(f(:h)),
"%s is not before h" % child.ref)
end
# Lastly, make sure our 'g' vertex made it into the relationship
# graph, since it's not involved in any relationships.
assert(graph.vertex?(f(:g)),
"Lost vertexes with no relations")
graph.to_jpg("normal_relations")
end
# Test pre-evaluation generation
def test_generate
mkgenerator() do
def generate
ret = []
if title.length > 1
ret << self.class.create(:title => title[0..-2])
else
return nil
end
ret
end
end
yay = Puppet::Type.newgenerator :title => "yay"
rah = Puppet::Type.newgenerator :title => "rah"
comp = newcomp(yay, rah)
trans = comp.evaluate
assert_nothing_raised do
trans.generate
end
%w{ya ra y r}.each do |name|
assert(trans.resources.vertex?(Puppet::Type.type(:generator)[name]),
"Generated %s was not a vertex" % name)
end
# Now make sure that cleanup gets rid of those generated types.
assert_nothing_raised do
trans.cleanup
end
%w{ya ra y r}.each do |name|
assert(!trans.resources.vertex?(Puppet::Type.type(:generator)[name]),
"Generated vertex %s was not removed from graph" % name)
assert_nil(Puppet::Type.type(:generator)[name],
"Generated vertex %s was not removed from class" % name)
end
end
# Test mid-evaluation generation.
def test_eval_generate
- $evaluated = {}
+ $evaluated = []
type = mkgenerator() do
def eval_generate
ret = []
if title.length > 1
ret << self.class.create(:title => title[0..-2])
else
return nil
end
ret
end
def evaluate
- $evaluated[self.title] = true
+ $evaluated << self.title
return []
end
end
yay = Puppet::Type.newgenerator :title => "yay"
rah = Puppet::Type.newgenerator :title => "rah", :subscribe => yay
comp = newcomp(yay, rah)
trans = comp.evaluate
trans.prepare
# Now apply the resources, and make sure they appropriately generate
# things.
assert_nothing_raised("failed to apply yay") do
- trans.apply(yay)
+ trans.eval_resource(yay)
end
ya = type["ya"]
assert(ya, "Did not generate ya")
assert(trans.relgraph.vertex?(ya),
"Did not add ya to rel_graph")
# Now make sure the appropriate relationships were added
assert(trans.relgraph.edge?(yay, ya),
"parent was not required by child")
assert(trans.relgraph.edge?(ya, rah),
"rah was not subscribed to ya")
# And make sure the relationship is a subscription with a callback,
# not just a require.
assert_equal({:callback => :refresh, :event => :ALL_EVENTS},
trans.relgraph[Puppet::Relationship.new(ya, rah)],
"The label was not retained")
# Now make sure it in turn eval_generates appropriately
assert_nothing_raised("failed to apply yay") do
- trans.apply(type["ya"])
+ trans.eval_resource(type["ya"])
end
%w{y}.each do |name|
res = type[name]
assert(res, "Did not generate %s" % name)
assert(trans.relgraph.vertex?(res),
"Did not add %s to rel_graph" % name)
end
assert_nothing_raised("failed to eval_generate with nil response") do
- trans.apply(type["y"])
+ trans.eval_resource(type["y"])
end
+ assert(trans.relgraph.edge?(yay, ya), "no edge was created for ya => yay")
- assert_equal(%w{yay ya y rah}, trans.sorted_resources.collect { |r| r.title },
- "Did not eval_generate correctly")
-
assert_nothing_raised("failed to apply rah") do
- trans.apply(rah)
+ trans.eval_resource(rah)
end
ra = type["ra"]
assert(ra, "Did not generate ra")
assert(trans.relgraph.vertex?(ra),
"Did not add ra to rel_graph" % name)
# Now make sure this generated resource has the same relationships as the generating
# resource
assert(trans.relgraph.edge?(yay, ra),
"yay is not required by ra")
assert(trans.relgraph.edge?(ya, ra),
"ra is not subscribed to ya")
# And make sure the relationship is a subscription with a callback,
# not just a require.
assert_equal({:callback => :refresh, :event => :ALL_EVENTS},
trans.relgraph[Puppet::Relationship.new(ya, ra)],
"The label was not retained")
# Now make sure that cleanup gets rid of those generated types.
assert_nothing_raised do
trans.cleanup
end
%w{ya ra y r}.each do |name|
assert(!trans.relgraph.vertex?(type[name]),
"Generated vertex %s was not removed from graph" % name)
assert_nil(type[name],
"Generated vertex %s was not removed from class" % name)
end
# Now, start over and make sure that everything gets evaluated.
trans = comp.evaluate
+ $evaluated.clear
assert_nothing_raised do
trans.evaluate
end
- assert_equal(%w{yay ya y rah ra r}.sort, $evaluated.keys.sort,
- "Not all resources were evaluated")
+ assert_equal(%w{yay ya y rah ra r}, $evaluated,
+ "Not all resources were evaluated or not in the right order")
end
def test_tags
res = Puppet::Type.newfile :path => tempfile()
comp = newcomp(res)
# Make sure they default to none
assert_equal([], comp.evaluate.tags)
# Make sure we get the main tags
Puppet[:tags] = %w{this is some tags}
assert_equal(%w{this is some tags}, comp.evaluate.tags)
# And make sure they get processed correctly
Puppet[:tags] = ["one", "two,three", "four"]
assert_equal(%w{one two three four}, comp.evaluate.tags)
# lastly, make sure we can override them
trans = comp.evaluate
trans.tags = ["one", "two,three", "four"]
assert_equal(%w{one two three four}, comp.evaluate.tags)
end
def test_tagged?
res = Puppet::Type.newfile :path => tempfile()
comp = newcomp(res)
trans = comp.evaluate
assert(trans.tagged?(res), "tagged? defaulted to false")
# Now set some tags
trans.tags = %w{some tags}
# And make sure it's false
assert(! trans.tagged?(res), "matched invalid tags")
# Set ignoretags and make sure it sticks
trans.ignoretags = true
assert(trans.tagged?(res), "tags were not ignored")
# Now make sure we actually correctly match tags
res[:tag] = "mytag"
trans.ignoretags = false
trans.tags = %w{notag}
assert(! trans.tagged?(res), "tags incorrectly matched")
trans.tags = %w{mytag yaytag}
assert(trans.tagged?(res), "tags should have matched")
end
# Make sure events propagate down the relationship graph appropriately.
def test_trigger
end
end
# $Id$
\ No newline at end of file
diff --git a/test/types/component.rb b/test/types/component.rb
index 3fdc7bfe2..3b55bab0f 100755
--- a/test/types/component.rb
+++ b/test/types/component.rb
@@ -1,316 +1,114 @@
#!/usr/bin/env ruby
$:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/
require 'puppet'
require 'puppettest'
require 'puppettest/support/resources'
# $Id$
class TestComponent < Test::Unit::TestCase
include PuppetTest
+ include PuppetTest::Support::Resources
def setup
super
@@used = {}
@type = Puppet::Type::Component
@file = Puppet::Type.type(:file)
end
def randnum(limit)
num = nil
looped = 0
loop do
looped += 1
if looped > 2000
raise "Reached limit of looping"
break
end
num = rand(limit)
unless @@used.include?(num)
@@used[num] = true
break
end
end
num
end
def mkfile(num = nil)
unless num
num = randnum(1000)
end
name = tempfile() + num.to_s
file = Puppet.type(:file).create(
:path => name,
:checksum => "md5"
)
@@tmpfiles << name
file
end
def mkcomp
Puppet.type(:component).create(:name => "component_" + randnum(1000).to_s)
end
def mkrandcomp(numfiles, numdivs)
comp = mkcomp
hash = {}
found = 0
divs = {}
numdivs.times { |i|
num = i + 2
divs[num] = nil
}
while found < numfiles
num = randnum(numfiles)
found += 1
f = mkfile(num)
hash[f.name] = f
reqd = []
divs.each { |n,obj|
if rand(50) % n == 0
if obj
unless reqd.include?(obj.object_id)
f[:require] = [[obj.class.name, obj.name]]
reqd << obj.object_id
end
end
end
divs[n] = f
}
end
hash.each { |name, obj|
comp.push obj
}
comp.finalize
comp
end
-
- def test_ordering
- list = nil
- comp = mkrandcomp(30,5)
- assert_nothing_raised {
- list = comp.flatten
- }
-
- list.each_with_index { |obj, index|
- obj.eachdependency { |dep|
- assert(list.index(dep) < index)
- }
- }
- end
def test_to_graph
one, two, middle, top = mktree
graph = nil
assert_nothing_raised do
graph = top.to_graph
end
assert(graph.is_a?(Puppet::PGraph), "result is not a pgraph")
[one, two, middle, top].each do |comp|
comp.each do |child|
assert(graph.edge?(comp, child),
"Did not create edge from %s => %s" % [comp.name, child.name])
end
end
end
-
- def test_correctsorting
- tmpfile = tempfile()
- @@tmpfiles.push tmpfile
- trans = nil
- cmd = nil
- File.open(tmpfile, File::WRONLY|File::CREAT|File::TRUNC) { |of|
- of.puts rand(100)
- }
- file = Puppet.type(:file).create(
- :path => tmpfile,
- :checksum => "md5"
- )
- assert_nothing_raised {
- cmd = Puppet.type(:exec).create(
- :command => "pwd",
- :path => "/usr/bin:/bin:/usr/sbin:/sbin",
- :subscribe => [[file.class.name,file.name]],
- :refreshonly => true
- )
- }
-
- order = nil
- assert_nothing_raised {
- order = Puppet.type(:component).sort([file, cmd])
- }
-
- [cmd, file].each { |obj|
- assert_equal(1, order.find_all { |o| o.name == obj.name }.length)
- }
- end
-
- def test_correctflattening
- tmpfile = tempfile()
- @@tmpfiles.push tmpfile
- trans = nil
- cmd = nil
- File.open(tmpfile, File::WRONLY|File::CREAT|File::TRUNC) { |of|
- of.puts rand(100)
- }
- file = Puppet.type(:file).create(
- :path => tmpfile,
- :checksum => "md5"
- )
- assert_nothing_raised {
- cmd = Puppet.type(:exec).create(
- :command => "pwd",
- :path => "/usr/bin:/bin:/usr/sbin:/sbin",
- :subscribe => [[file.class.name,file.name]],
- :refreshonly => true
- )
- }
-
- comp = newcomp(cmd, file)
- comp.finalize
- objects = nil
- assert_nothing_raised {
- objects = comp.flatten
- }
-
- [cmd, file].each { |obj|
- assert_equal(1, objects.find_all { |o| o.name == obj.name }.length)
- }
-
- assert(objects[0] == file, "File was not first object")
- assert(objects[1] == cmd, "Exec was not second object")
- end
-
- def test_deepflatten
- tmpfile = tempfile()
- @@tmpfiles.push tmpfile
- trans = nil
- cmd = nil
- File.open(tmpfile, File::WRONLY|File::CREAT|File::TRUNC) { |of|
- of.puts rand(100)
- }
- file = Puppet.type(:file).create(
- :path => tmpfile,
- :checksum => "md5"
- )
- assert_nothing_raised {
- cmd = Puppet.type(:exec).create(
- :command => "pwd",
- :path => "/usr/bin:/bin:/usr/sbin:/sbin",
- :refreshonly => true
- )
- }
-
- fcomp = newcomp("fflatten", file)
- ecomp = newcomp("eflatten", cmd)
-
- # this subscription can screw up the sorting
- ecomp[:subscribe] = [[fcomp.class.name,fcomp.name]]
-
- comp = newcomp("bflatten", ecomp, fcomp)
- comp.finalize
- objects = nil
- assert_nothing_raised {
- objects = comp.flatten
- }
-
- assert_equal(objects.length, 2, "Did not get two sorted objects")
- objects.each { |o|
- assert(o.is_a?(Puppet::Type), "Object %s is not a Type" % o.class)
- }
-
- assert(objects[0] == file, "File was not first object")
- assert(objects[1] == cmd, "Exec was not second object")
- end
-
- def test_deepflatten2
- tmpfile = tempfile()
- @@tmpfiles.push tmpfile
- trans = nil
- cmd = nil
- File.open(tmpfile, File::WRONLY|File::CREAT|File::TRUNC) { |of|
- of.puts rand(100)
- }
- file = Puppet.type(:file).create(
- :path => tmpfile,
- :checksum => "md5"
- )
- assert_nothing_raised {
- cmd = Puppet.type(:exec).create(
- :command => "pwd",
- :path => "/usr/bin:/bin:/usr/sbin:/sbin",
- :refreshonly => true
- )
- }
-
- ocmd = nil
- assert_nothing_raised {
- ocmd = Puppet.type(:exec).create(
- :command => "echo true",
- :path => "/usr/bin:/bin:/usr/sbin:/sbin",
- :refreshonly => true
- )
- }
-
- fcomp = newcomp("fflatten", file)
- ecomp = newcomp("eflatten", cmd)
- ocomp = newcomp("oflatten", ocmd)
-
- # this subscription can screw up the sorting
- cmd[:subscribe] = [[fcomp.class.name,fcomp.name]]
- ocmd[:subscribe] = [[cmd.class.name,cmd.name]]
-
- comp = newcomp("bflatten", ocomp, ecomp, fcomp)
- comp.finalize
- objects = nil
- assert_nothing_raised {
- objects = comp.flatten
- }
-
- assert_equal(objects.length, 3, "Did not get three sorted objects")
-
- objects.each { |o|
- assert(o.is_a?(Puppet::Type), "Object %s is not a Type" % o.class)
- }
-
- assert(objects[0] == file, "File was not first object")
- assert(objects[1] == cmd, "Exec was not second object")
- assert(objects[2] == ocmd, "Other exec was not second object")
- end
-
- def test_moreordering
- dir = tempfile()
-
- comp = Puppet.type(:component).create(
- :name => "ordertesting"
- )
-
- 10.times { |i|
- fileobj = Puppet.type(:file).create(
- :path => File.join(dir, "file%s" % i),
- :ensure => "file"
- )
- comp.push(fileobj)
- }
-
- dirobj = Puppet.type(:file).create(
- :path => dir,
- :ensure => "directory"
- )
-
- comp.push(dirobj)
-
- assert_apply(comp)
- end
end
diff --git a/test/types/exec.rb b/test/types/exec.rb
index 305f27d56..fd304e7d1 100755
--- a/test/types/exec.rb
+++ b/test/types/exec.rb
@@ -1,603 +1,608 @@
#!/usr/bin/env ruby
$:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/
require 'puppet'
require 'puppettest'
require 'facter'
class TestExec < Test::Unit::TestCase
include PuppetTest
def test_execution
command = nil
output = nil
assert_nothing_raised {
command = Puppet.type(:exec).create(
:command => "/bin/echo"
)
}
assert_nothing_raised {
command.evaluate
}
assert_events([:executed_command], command)
end
def test_numvsstring
[0, "0"].each { |val|
Puppet.type(:exec).clear
Puppet.type(:component).clear
command = nil
output = nil
assert_nothing_raised {
command = Puppet.type(:exec).create(
:command => "/bin/echo",
:returns => val
)
}
assert_events([:executed_command], command)
}
end
def test_path_or_qualified
command = nil
output = nil
assert_raise(Puppet::Error) {
command = Puppet.type(:exec).create(
:command => "echo"
)
}
assert_nothing_raised {
command = Puppet.type(:exec).create(
:command => "echo",
:path => "/usr/bin:/bin:/usr/sbin:/sbin"
)
}
Puppet.type(:exec).clear
assert_nothing_raised {
command = Puppet.type(:exec).create(
:command => "/bin/echo"
)
}
Puppet.type(:exec).clear
assert_nothing_raised {
command = Puppet.type(:exec).create(
:command => "/bin/echo",
:path => "/usr/bin:/bin:/usr/sbin:/sbin"
)
}
end
def test_nonzero_returns
assert_nothing_raised {
command = Puppet.type(:exec).create(
:command => "mkdir /this/directory/does/not/exist",
:path => "/usr/bin:/bin:/usr/sbin:/sbin",
:returns => 1
)
}
assert_nothing_raised {
command = Puppet.type(:exec).create(
:command => "touch /etc",
:path => "/usr/bin:/bin:/usr/sbin:/sbin",
:returns => 1
)
}
assert_nothing_raised {
command = Puppet.type(:exec).create(
:command => "thiscommanddoesnotexist",
:path => "/usr/bin:/bin:/usr/sbin:/sbin",
:returns => 127
)
}
end
def test_cwdsettings
command = nil
dir = "/tmp"
wd = Dir.chdir(dir) {
Dir.getwd
}
assert_nothing_raised {
command = Puppet.type(:exec).create(
:command => "pwd",
:cwd => dir,
:path => "/usr/bin:/bin:/usr/sbin:/sbin",
:returns => 0
)
}
assert_events([:executed_command], command)
assert_equal(wd,command.output.chomp)
end
def test_refreshonly_functional
file = nil
cmd = nil
tmpfile = tempfile()
@@tmpfiles.push tmpfile
trans = nil
File.open(tmpfile, File::WRONLY|File::CREAT|File::TRUNC) { |of|
of.puts rand(100)
}
file = Puppet.type(:file).create(
:path => tmpfile,
:checksum => "md5"
)
assert_instance_of(Puppet.type(:file), file)
assert_nothing_raised {
cmd = Puppet.type(:exec).create(
:command => "pwd",
:path => "/usr/bin:/bin:/usr/sbin:/sbin",
:subscribe => [[file.class.name,file.name]],
:refreshonly => true
)
}
assert_instance_of(Puppet.type(:exec), cmd)
comp = Puppet.type(:component).create(:name => "RefreshTest")
[file,cmd].each { |obj|
comp.push obj
}
events = nil
assert_nothing_raised {
trans = comp.evaluate
file.retrieve
sum = file.state(:checksum)
assert(sum.insync?, "checksum is not in sync")
events = trans.evaluate.collect { |event|
event.event
}
}
# the first checksum shouldn't result in a changed file
assert_equal([],events)
File.open(tmpfile, File::WRONLY|File::CREAT|File::TRUNC) { |of|
of.puts rand(100)
of.puts rand(100)
of.puts rand(100)
}
assert_nothing_raised {
trans = comp.evaluate
sum = file.state(:checksum)
events = trans.evaluate.collect { |event| event.event }
}
# verify that only the file_changed event was kicked off, not the
# command_executed
assert_equal(
[:file_changed, :triggered],
events
)
end
def test_refreshonly
cmd = true
assert_nothing_raised {
cmd = Puppet.type(:exec).create(
:command => "pwd",
:path => "/usr/bin:/bin:/usr/sbin:/sbin",
:refreshonly => true
)
}
# Checks should always fail when refreshonly is enabled
assert(!cmd.check, "Check passed with refreshonly true")
# Now set it to false
cmd[:refreshonly] = false
assert(cmd.check, "Check failed with refreshonly false")
end
def test_creates
file = tempfile()
exec = nil
assert(! FileTest.exists?(file), "File already exists")
assert_nothing_raised {
exec = Puppet.type(:exec).create(
:command => "touch %s" % file,
:path => "/usr/bin:/bin:/usr/sbin:/sbin",
:creates => file
)
}
comp = newcomp("createstest", exec)
assert_events([:executed_command], comp, "creates")
assert_events([], comp, "creates")
end
# Verify that we can download the file that we're going to execute.
def test_retrievethenmkexe
exe = tempfile()
oexe = tempfile()
sh = %x{which sh}
File.open(exe, "w") { |f| f.puts "#!#{sh}\necho yup" }
file = Puppet.type(:file).create(
:path => oexe,
:source => exe,
:mode => 0755
)
exec = Puppet.type(:exec).create(
:command => oexe,
:require => [:file, oexe]
)
comp = newcomp("Testing", file, exec)
assert_events([:file_created, :executed_command], comp)
end
# Verify that we auto-require any managed scripts.
def test_autorequire
exe = tempfile()
oexe = tempfile()
sh = %x{which sh}
File.open(exe, "w") { |f| f.puts "#!#{sh}\necho yup" }
file = Puppet.type(:file).create(
:path => oexe,
:source => exe,
:mode => 755
)
basedir = File.dirname(oexe)
baseobj = Puppet.type(:file).create(
:path => basedir,
:source => exe,
:mode => 755
)
ofile = Puppet.type(:file).create(
:path => exe,
:mode => 755
)
exec = Puppet.type(:exec).create(
:command => oexe,
:path => ENV["PATH"],
:cwd => basedir
)
cat = Puppet.type(:exec).create(
:command => "cat %s %s" % [exe, oexe],
:path => ENV["PATH"]
)
-
- comp = newcomp(ofile, exec, cat, file, baseobj)
- comp.finalize
+
+ rels = nil
+ assert_nothing_raised do
+ rels = exec.autorequire
+ end
# Verify we get the script itself
- assert(exec.requires?(file), "Exec did not autorequire %s" % file)
+ assert(rels.detect { |r| r.source == file }, "Exec did not autorequire its command")
# Verify we catch the cwd
- assert(exec.requires?(baseobj), "Exec did not autorequire cwd")
+ assert(rels.detect { |r| r.source == baseobj }, "Exec did not autorequire its cwd")
# Verify we don't require ourselves
+ assert(! rels.detect { |r| r.source == ofile }, "Exec incorrectly required mentioned file")
assert(!exec.requires?(ofile), "Exec incorrectly required file")
- # Verify that we catch inline files
# We not longer autorequire inline files
- assert(! cat.requires?(ofile), "Exec required second inline file")
- assert(! cat.requires?(file), "Exec required inline file")
+ assert_nothing_raised do
+ rels = cat.autorequire
+ end
+ assert(! rels.detect { |r| r.source == ofile }, "Exec required second inline file")
+ assert(! rels.detect { |r| r.source == file }, "Exec required inline file")
end
def test_ifonly
afile = tempfile()
bfile = tempfile()
exec = nil
assert_nothing_raised {
exec = Puppet.type(:exec).create(
:command => "touch %s" % bfile,
:onlyif => "test -f %s" % afile,
:path => ENV['PATH']
)
}
assert_events([], exec)
system("touch %s" % afile)
assert_events([:executed_command], exec)
assert_events([:executed_command], exec)
system("rm %s" % afile)
assert_events([], exec)
end
def test_unless
afile = tempfile()
bfile = tempfile()
exec = nil
assert_nothing_raised {
exec = Puppet.type(:exec).create(
:command => "touch %s" % bfile,
:unless => "test -f %s" % afile,
:path => ENV['PATH']
)
}
comp = newcomp(exec)
assert_events([:executed_command], comp)
assert_events([:executed_command], comp)
system("touch %s" % afile)
assert_events([], comp)
assert_events([], comp)
system("rm %s" % afile)
assert_events([:executed_command], comp)
assert_events([:executed_command], comp)
end
if Puppet::SUIDManager.uid == 0
# Verify that we can execute commands as a special user
def mknverify(file, user, group = nil, id = true)
args = {
:command => "touch %s" % file,
:path => "/usr/bin:/bin:/usr/sbin:/sbin",
}
if user
#Puppet.warning "Using user %s" % user.name
if id
# convert to a string, because that's what the object expects
args[:user] = user.uid.to_s
else
args[:user] = user.name
end
end
if group
#Puppet.warning "Using group %s" % group.name
if id
args[:group] = group.gid.to_s
else
args[:group] = group.name
end
end
exec = nil
assert_nothing_raised {
exec = Puppet.type(:exec).create(args)
}
comp = newcomp("usertest", exec)
assert_events([:executed_command], comp, "usertest")
assert(FileTest.exists?(file), "File does not exist")
if user
assert_equal(user.uid, File.stat(file).uid, "File UIDs do not match")
end
# We can't actually test group ownership, unfortunately, because
# behaviour changes wildlly based on platform.
Puppet::Type.allclear
end
def test_userngroup
file = tempfile()
[
[nonrootuser()], # just user, by name
[nonrootuser(), nil, true], # user, by uid
[nil, nonrootgroup()], # just group
[nil, nonrootgroup(), true], # just group, by id
[nonrootuser(), nonrootgroup()], # user and group, by name
[nonrootuser(), nonrootgroup(), true], # user and group, by id
].each { |ary|
mknverify(file, *ary) {
}
}
end
end
def test_logoutput
exec = nil
assert_nothing_raised {
exec = Puppet.type(:exec).create(
:title => "logoutputesting",
:path => "/usr/bin:/bin",
:command => "echo logoutput is false",
:logoutput => false
)
}
assert_apply(exec)
assert_nothing_raised {
exec[:command] = "echo logoutput is true"
exec[:logoutput] = true
}
assert_apply(exec)
assert_nothing_raised {
exec[:command] = "echo logoutput is warning"
exec[:logoutput] = "warning"
}
assert_apply(exec)
end
def test_execthenfile
exec = nil
file = nil
basedir = tempfile()
path = File.join(basedir, "subfile")
assert_nothing_raised {
exec = Puppet.type(:exec).create(
:title => "mkdir",
:path => "/usr/bin:/bin",
:creates => basedir,
:command => "mkdir %s; touch %s" % [basedir, path]
)
}
assert_nothing_raised {
file = Puppet.type(:file).create(
:path => basedir,
:recurse => true,
:mode => "755",
:require => ["exec", "mkdir"]
)
}
comp = newcomp(file, exec)
comp.finalize
assert_events([:executed_command, :file_changed], comp)
assert(FileTest.exists?(path), "Exec ran first")
assert(File.stat(path).mode & 007777 == 0755)
end
def test_falsevals
exec = nil
assert_nothing_raised do
exec = Puppet.type(:exec).create(
:command => "/bin/touch yayness"
)
end
Puppet.type(:exec).checks.each do |check|
klass = Puppet.type(:exec).paramclass(check)
next if klass.values.include? :false
assert_raise(Puppet::Error, "Check %s did not fail on false" % check) do
exec[check] = false
end
end
end
def test_createcwdandexe
exec1 = exec2 = nil
dir = tempfile()
file = tempfile()
assert_nothing_raised {
exec1 = Puppet.type(:exec).create(
:path => ENV["PATH"],
:command => "mkdir #{dir}"
)
}
assert_nothing_raised("Could not create exec w/out existing cwd") {
exec2 = Puppet.type(:exec).create(
:path => ENV["PATH"],
:command => "touch #{file}",
:cwd => dir
)
}
# Throw a check in there with our cwd and make sure it works
assert_nothing_raised("Could not check with a missing cwd") do
exec2[:unless] = "test -f /this/file/does/not/exist"
exec2.retrieve
end
assert_raise(Puppet::Error) do
exec2.state(:returns).sync
end
assert_nothing_raised do
exec2[:require] = ["exec", exec1.name]
exec2.finish
end
assert_apply(exec1, exec2)
assert(FileTest.exists?(file))
end
def test_checkarrays
exec = nil
file = tempfile()
test = "test -f #{file}"
assert_nothing_raised {
exec = Puppet.type(:exec).create(
:path => ENV["PATH"],
:command => "touch #{file}"
)
}
assert_nothing_raised {
exec[:unless] = test
}
assert_nothing_raised {
assert(exec.check, "Check did not pass")
}
assert_nothing_raised {
exec[:unless] = [test, test]
}
assert_nothing_raised {
exec.finish
}
assert_nothing_raised {
assert(exec.check, "Check did not pass")
}
assert_apply(exec)
assert_nothing_raised {
assert(! exec.check, "Check passed")
}
end
def test_missing_checks_cause_failures
# Solaris's sh exits with 1 here instead of 127
return if Facter.value(:operatingsystem) == "Solaris"
exec = Puppet::Type.newexec(
:command => "echo true",
:path => ENV["PATH"],
:onlyif => "/bin/nosuchthingexists"
)
assert_raise(ArgumentError, "Missing command did not raise error") {
exec.run("/bin/nosuchthingexists")
}
end
def test_envparam
exec = Puppet::Type.newexec(
:command => "echo $envtest",
:path => ENV["PATH"],
:env => "envtest=yayness"
)
assert(exec, "Could not make exec")
output = status = nil
assert_nothing_raised {
output, status = exec.run("echo $envtest")
}
assert_equal("yayness\n", output)
# Now check whether we can do multiline settings
assert_nothing_raised do
exec[:env] = "envtest=a list of things
and stuff"
end
output = status = nil
assert_nothing_raised {
output, status = exec.run('echo "$envtest"')
}
assert_equal("a list of things\nand stuff\n", output)
# Now test arrays
assert_nothing_raised do
exec[:env] = ["funtest=A", "yaytest=B"]
end
output = status = nil
assert_nothing_raised {
output, status = exec.run('echo "$funtest" "$yaytest"')
}
assert_equal("A B\n", output)
end
end
# $Id$
diff --git a/test/types/file.rb b/test/types/file.rb
index d5b493788..c57129cd5 100755
--- a/test/types/file.rb
+++ b/test/types/file.rb
@@ -1,1795 +1,1766 @@
#!/usr/bin/env ruby
$:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/
require 'puppet'
require 'fileutils'
require 'puppettest'
class TestFile < Test::Unit::TestCase
include PuppetTest::FileTesting
# hmmm
# this is complicated, because we store references to the created
# objects in a central store
def mkfile(hash)
file = nil
assert_nothing_raised {
file = Puppet.type(:file).create(hash)
}
return file
end
def mktestfile
# because luke's home directory is on nfs, it can't be used for testing
# as root
tmpfile = tempfile()
File.open(tmpfile, "w") { |f| f.puts rand(100) }
@@tmpfiles.push tmpfile
mkfile(:name => tmpfile)
end
def setup
super
@file = Puppet::Type.type(:file)
begin
initstorage
rescue
system("rm -rf %s" % Puppet[:statefile])
end
end
def teardown
Puppet::Storage.clear
system("rm -rf %s" % Puppet[:statefile])
super
end
def initstorage
Puppet::Storage.init
Puppet::Storage.load
end
def clearstorage
Puppet::Storage.store
Puppet::Storage.clear
end
def test_owner
file = mktestfile()
users = {}
count = 0
# collect five users
Etc.passwd { |passwd|
if count > 5
break
else
count += 1
end
users[passwd.uid] = passwd.name
}
fake = {}
# find a fake user
while true
a = rand(1000)
begin
Etc.getpwuid(a)
rescue
fake[a] = "fakeuser"
break
end
end
uid, name = users.shift
us = {}
us[uid] = name
users.each { |uid, name|
assert_apply(file)
assert_nothing_raised() {
file[:owner] = name
}
assert_nothing_raised() {
file.retrieve
}
assert_apply(file)
}
end
def test_group
file = mktestfile()
[%x{groups}.chomp.split(/ /), Process.groups].flatten.each { |group|
assert_nothing_raised() {
file[:group] = group
}
assert(file.state(:group))
assert(file.state(:group).should)
}
end
if Puppet::SUIDManager.uid == 0
def test_createasuser
dir = tmpdir()
user = nonrootuser()
path = File.join(tmpdir, "createusertesting")
@@tmpfiles << path
file = nil
assert_nothing_raised {
file = Puppet.type(:file).create(
:path => path,
:owner => user.name,
:ensure => "file",
:mode => "755"
)
}
comp = newcomp("createusertest", file)
assert_events([:file_created], comp)
end
def test_nofollowlinks
basedir = tempfile()
Dir.mkdir(basedir)
file = File.join(basedir, "file")
link = File.join(basedir, "link")
File.open(file, "w", 0644) { |f| f.puts "yayness"; f.flush }
File.symlink(file, link)
# First test 'user'
user = nonrootuser()
inituser = File.lstat(link).uid
File.lchown(inituser, nil, link)
obj = nil
assert_nothing_raised {
obj = Puppet.type(:file).create(
:title => link,
:owner => user.name
)
}
obj.retrieve
# Make sure it defaults to managing the link
assert_events([:file_changed], obj)
assert_equal(user.uid, File.lstat(link).uid)
assert_equal(inituser, File.stat(file).uid)
File.chown(inituser, nil, file)
File.lchown(inituser, nil, link)
# Try following
obj[:links] = :follow
assert_events([:file_changed], obj)
assert_equal(user.uid, File.stat(file).uid)
assert_equal(inituser, File.lstat(link).uid)
# And then explicitly managing
File.chown(inituser, nil, file)
File.lchown(inituser, nil, link)
obj[:links] = :manage
assert_events([:file_changed], obj)
assert_equal(user.uid, File.lstat(link).uid)
assert_equal(inituser, File.stat(file).uid)
obj.delete(:owner)
obj[:links] = :ignore
# And then test 'group'
group = nonrootgroup
initgroup = File.stat(file).gid
obj[:group] = group.name
assert_events([:file_changed], obj)
assert_equal(initgroup, File.stat(file).gid)
assert_equal(group.gid, File.lstat(link).gid)
File.chown(nil, initgroup, file)
File.lchown(nil, initgroup, link)
obj[:links] = :follow
assert_events([:file_changed], obj)
assert_equal(group.gid, File.stat(file).gid)
File.chown(nil, initgroup, file)
File.lchown(nil, initgroup, link)
obj[:links] = :manage
assert_events([:file_changed], obj)
assert_equal(group.gid, File.lstat(link).gid)
assert_equal(initgroup, File.stat(file).gid)
end
def test_ownerasroot
file = mktestfile()
users = {}
count = 0
# collect five users
Etc.passwd { |passwd|
if count > 5
break
else
count += 1
end
next if passwd.uid < 0
users[passwd.uid] = passwd.name
}
fake = {}
# find a fake user
while true
a = rand(1000)
begin
Etc.getpwuid(a)
rescue
fake[a] = "fakeuser"
break
end
end
users.each { |uid, name|
assert_nothing_raised() {
file[:owner] = name
}
changes = []
assert_nothing_raised() {
changes << file.evaluate
}
assert(changes.length > 0)
assert_apply(file)
file.retrieve
assert(file.insync?())
assert_nothing_raised() {
file[:owner] = uid
}
assert_apply(file)
file.retrieve
# make sure changing to number doesn't cause a sync
assert(file.insync?())
}
# We no longer raise an error here, because we check at run time
#fake.each { |uid, name|
# assert_raise(Puppet::Error) {
# file[:owner] = name
# }
# assert_raise(Puppet::Error) {
# file[:owner] = uid
# }
#}
end
def test_groupasroot
file = mktestfile()
[%x{groups}.chomp.split(/ /), Process.groups].flatten.each { |group|
assert_nothing_raised() {
file[:group] = group
}
assert(file.state(:group))
assert(file.state(:group).should)
assert_apply(file)
file.retrieve
assert(file.insync?())
assert_nothing_raised() {
file.delete(:group)
}
}
end
if Facter.value(:operatingsystem) == "Darwin"
def test_sillyowner
file = tempfile()
File.open(file, "w") { |f| f.puts "" }
File.chown(-2, nil, file)
assert(File.stat(file).uid > 120000, "eh?")
user = nonrootuser
obj = Puppet::Type.newfile(
:path => file,
:owner => user.name
)
assert_apply(obj)
assert_equal(user.uid, File.stat(file).uid)
end
end
else
$stderr.puts "Run as root for complete owner and group testing"
end
def test_create
%w{a b c d}.collect { |name| tempfile() + name.to_s }.each { |path|
file =nil
assert_nothing_raised() {
file = Puppet.type(:file).create(
:name => path,
:ensure => "file"
)
}
assert_events([:file_created], file)
assert_events([], file)
assert(FileTest.file?(path), "File does not exist")
assert(file.insync?())
@@tmpfiles.push path
}
end
def test_create_dir
basedir = tempfile()
Dir.mkdir(basedir)
%w{a b c d}.collect { |name| "#{basedir}/%s" % name }.each { |path|
file = nil
assert_nothing_raised() {
file = Puppet.type(:file).create(
:name => path,
:ensure => "directory"
)
}
assert(! FileTest.directory?(path), "Directory %s already exists" %
[path])
assert_events([:directory_created], file)
assert_events([], file)
assert(file.insync?())
assert(FileTest.directory?(path))
@@tmpfiles.push path
}
end
def test_modes
file = mktestfile
# Set it to something else initially
File.chmod(0775, file.title)
[0644,0755,0777,0641].each { |mode|
assert_nothing_raised() {
file[:mode] = mode
}
assert_events([:file_changed], file)
assert_events([], file)
assert(file.insync?())
assert_nothing_raised() {
file.delete(:mode)
}
}
end
def test_checksums
types = %w{md5 md5lite timestamp time}
exists = "/tmp/sumtest-exists"
nonexists = "/tmp/sumtest-nonexists"
@@tmpfiles << exists
@@tmpfiles << nonexists
# try it both with files that exist and ones that don't
files = [exists, nonexists]
initstorage
File.open(exists,File::CREAT|File::TRUNC|File::WRONLY) { |of|
of.puts "initial text"
}
types.each { |type|
files.each { |path|
if Puppet[:debug]
Puppet.warning "Testing %s on %s" % [type,path]
end
file = nil
events = nil
# okay, we now know that we have a file...
assert_nothing_raised() {
file = Puppet.type(:file).create(
:name => path,
:ensure => "file",
:checksum => type
)
}
trans = nil
file.retrieve
if file.title !~ /nonexists/
sum = file.state(:checksum)
assert(sum.insync?, "file is not in sync")
end
events = assert_apply(file)
assert(! events.include?(:file_changed),
"File incorrectly changed")
assert_events([], file)
# We have to sleep because the time resolution of the time-based
# mechanisms is greater than one second
sleep 1 if type =~ /time/
assert_nothing_raised() {
File.open(path,File::CREAT|File::TRUNC|File::WRONLY) { |of|
of.puts "some more text, yo"
}
}
Puppet.type(:file).clear
# now recreate the file
assert_nothing_raised() {
file = Puppet.type(:file).create(
:name => path,
:checksum => type
)
}
trans = nil
assert_events([:file_changed], file)
# Run it a few times to make sure we aren't getting
# spurious changes.
assert_nothing_raised do
file.state(:checksum).retrieve
end
assert(file.state(:checksum).insync?,
"checksum is not in sync")
sleep 1.1 if type =~ /time/
assert_nothing_raised() {
File.unlink(path)
File.open(path,File::CREAT|File::TRUNC|File::WRONLY) { |of|
# We have to put a certain amount of text in here or
# the md5-lite test fails
2.times {
of.puts rand(100)
}
of.flush
}
}
assert_events([:file_changed], file)
# verify that we're actually getting notified when a file changes
assert_nothing_raised() {
Puppet.type(:file).clear
}
if path =~ /nonexists/
File.unlink(path)
end
}
}
end
def cyclefile(path)
# i had problems with using :name instead of :path
[:name,:path].each { |param|
file = nil
changes = nil
comp = nil
trans = nil
initstorage
assert_nothing_raised {
file = Puppet.type(:file).create(
param => path,
:recurse => true,
:checksum => "md5"
)
}
comp = Puppet.type(:component).create(
:name => "component"
)
comp.push file
assert_nothing_raised {
trans = comp.evaluate
}
assert_nothing_raised {
trans.evaluate
}
clearstorage
Puppet::Type.allclear
}
end
def test_localrecurse
# Create a test directory
path = tempfile()
dir = @file.create :path => path, :mode => 0755, :recurse => true
Dir.mkdir(path)
# Make sure we return nothing when there are no children
ret = nil
assert_nothing_raised() { ret = dir.localrecurse(true) }
assert_equal([], ret, "empty dir returned children")
# Now make a file and make sure we get it
test = File.join(path, "file")
File.open(test, "w") { |f| f.puts "yay" }
assert_nothing_raised() { ret = dir.localrecurse(true) }
fileobj = @file[test]
assert(fileobj, "child object was not created")
assert_equal([fileobj], ret, "child object was not returned")
# check that the file lists us as a dependency
assert_equal([[:file, dir.title]], fileobj[:require], "dependency was not set up")
# And that it inherited our recurse setting
assert_equal(true, fileobj[:recurse], "file did not inherit recurse")
# Make sure it's not returned again
assert_nothing_raised() { ret = dir.localrecurse(true) }
assert_equal([], ret, "child object was returned twice")
# Now just for completion, make sure we will return many files
files = []
10.times do |i|
f = File.join(path, i.to_s)
files << f
File.open(f, "w") do |o| o.puts "" end
end
assert_nothing_raised() { ret = dir.localrecurse(true) }
assert_equal(files.sort, ret.collect { |f| f.title }, "child object was returned twice")
# Clean everything up and start over
files << test
files.each do |f| File.unlink(f) end
# Now make sure we correctly ignore things
dir[:ignore] = "*.out"
bad = File.join(path, "test.out")
good = File.join(path, "yayness")
[good, bad].each do |f|
File.open(f, "w") { |o| o.puts "" }
end
assert_nothing_raised() { ret = dir.localrecurse(true) }
assert_equal([good], ret.collect { |f| f.title }, "ignore failed")
# Now make sure purging works
dir[:purge] = true
dir[:ignore] = "svn"
assert_nothing_raised() { ret = dir.localrecurse(true) }
assert_equal([bad], ret.collect { |f| f.title }, "purge failed")
badobj = @file[bad]
assert(badobj, "did not create bad object")
assert_equal(:absent, badobj.should(:ensure), "ensure was not set to absent on bad object")
end
def test_recurse
basedir = tempfile()
FileUtils.mkdir_p(basedir)
# Create our file
dir = nil
assert_nothing_raised {
dir = Puppet.type(:file).create(
:path => basedir,
:check => %w{owner mode group}
)
}
return_nil = false
# and monkey-patch it
[:localrecurse, :sourcerecurse, :linkrecurse].each do |m|
dir.meta_def(m) do |recurse|
if return_nil # for testing nil return, of course
return nil
else
return [recurse]
end
end
end
# First try it with recurse set to false
dir[:recurse] = false
assert_nothing_raised do
assert_nil(dir.recurse)
end
# Now try it with the different valid positive values
[true, "true", "inf", 50].each do |value|
assert_nothing_raised { dir[:recurse] = value}
# Now make sure the methods are called appropriately
ret = nil
assert_nothing_raised do
ret = dir.recurse
end
# We should only call the localrecurse method, so make sure
# that's the case
if value == 50
# Make sure our counter got decremented
assert_equal([49], ret, "did not call localrecurse")
else
assert_equal([true], ret, "did not call localrecurse")
end
end
# Make sure it doesn't recurse when we've set recurse to false
[false, "false"].each do |value|
assert_nothing_raised { dir[:recurse] = value }
ret = nil
assert_nothing_raised() { ret = dir.recurse }
assert_nil(ret)
end
dir[:recurse] = true
# Now add a target, so we do the linking thing
dir[:target] = tempfile()
ret = nil
assert_nothing_raised { ret = dir.recurse }
assert_equal([true, true], ret, "did not call linkrecurse")
# And add a source, and make sure we call that
dir[:source] = tempfile()
assert_nothing_raised { ret = dir.recurse }
assert_equal([true, true, true], ret, "did not call linkrecurse")
# Lastly, make sure we correctly handle returning nil
return_nil = true
assert_nothing_raised { ret = dir.recurse }
end
def test_recurse?
file = Puppet::Type.type(:file).create :path => tempfile
# Make sure we default to false
assert(! file.recurse?, "Recurse defaulted to true")
[true, "true", 10, "inf"].each do |value|
file[:recurse] = value
assert(file.recurse?, "%s did not cause recursion" % value)
end
[false, "false", 0].each do |value|
file[:recurse] = value
assert(! file.recurse?, "%s caused recursion" % value)
end
end
def test_recursion
basedir = tempfile()
subdir = File.join(basedir, "subdir")
tmpfile = File.join(basedir,"testing")
FileUtils.mkdir_p(subdir)
dir = nil
[true, "true", "inf", 50].each do |value|
assert_nothing_raised {
dir = Puppet.type(:file).create(
:path => basedir,
:recurse => value,
:check => %w{owner mode group}
)
}
children = nil
assert_nothing_raised {
children = dir.eval_generate
}
assert_equal([subdir], children.collect {|c| c.title },
"Incorrect generated children")
dir.class[subdir].remove
File.open(tmpfile, "w") { |f| f.puts "yayness" }
assert_nothing_raised {
children = dir.eval_generate
}
assert_equal([subdir, tmpfile].sort, children.collect {|c| c.title }.sort,
"Incorrect generated children")
File.unlink(tmpfile)
#system("rm -rf %s" % basedir)
Puppet.type(:file).clear
end
end
def test_filetype_retrieval
file = nil
# Verify it retrieves files of type directory
assert_nothing_raised {
file = Puppet.type(:file).create(
:name => tmpdir(),
:check => :type
)
}
assert_nothing_raised {
file.evaluate
}
assert_equal("directory", file.state(:type).is)
# And then check files
assert_nothing_raised {
file = Puppet.type(:file).create(
:name => tempfile(),
:ensure => "file"
)
}
assert_apply(file)
file[:check] = "type"
assert_apply(file)
assert_equal("file", file.state(:type).is)
file[:type] = "directory"
assert_nothing_raised { file.retrieve }
# The 'retrieve' method sets @should to @is, so they're never
# out of sync. It's a read-only class.
assert(file.insync?)
end
def test_remove
basedir = tempfile()
subdir = File.join(basedir, "this")
FileUtils.mkdir_p(subdir)
dir = nil
assert_nothing_raised {
dir = Puppet.type(:file).create(
:path => basedir,
:recurse => true,
:check => %w{owner mode group}
)
}
assert_nothing_raised {
dir.eval_generate
}
obj = nil
assert_nothing_raised {
obj = Puppet.type(:file)[subdir]
}
assert(obj, "Could not retrieve subdir object")
assert_nothing_raised {
obj.remove(true)
}
assert_nothing_raised {
obj = Puppet.type(:file)[subdir]
}
assert_nil(obj, "Retrieved removed object")
end
def test_path
dir = tempfile()
path = File.join(dir, "subdir")
assert_nothing_raised("Could not make file") {
FileUtils.mkdir_p(File.dirname(path))
File.open(path, "w") { |f| f.puts "yayness" }
}
file = nil
dirobj = nil
assert_nothing_raised("Could not make file object") {
dirobj = Puppet.type(:file).create(
:path => dir,
:recurse => true,
:check => %w{mode owner group}
)
}
assert_nothing_raised {
- dirobj.generate
+ dirobj.eval_generate
}
assert_nothing_raised {
file = dirobj.class[path]
}
assert(file, "Could not retrieve file object")
assert_equal("file=%s" % file.title, file.path)
end
def test_autorequire
basedir = tempfile()
subfile = File.join(basedir, "subfile")
baseobj = Puppet.type(:file).create(
:name => basedir,
:ensure => "directory"
)
subobj = Puppet.type(:file).create(
:name => subfile,
:ensure => "file"
)
- comp = newcomp(baseobj, subobj)
- comp.finalize
-
- assert(subobj.requires?(baseobj), "File did not require basedir")
- assert(!subobj.requires?(subobj), "File required itself")
- assert_events([:directory_created, :file_created], comp)
+ edge = nil
+ assert_nothing_raised do
+ edge = subobj.autorequire.shift
+ end
+ assert_equal(baseobj, edge.source, "file did not require its parent dir")
+ assert_equal(subobj, edge.target, "file did not require its parent dir")
end
def test_content
file = tempfile()
str = "This is some content"
obj = nil
assert_nothing_raised {
obj = Puppet.type(:file).create(
:name => file,
:content => str
)
}
assert(!obj.insync?, "Object is incorrectly in sync")
assert_events([:file_created], obj)
obj.retrieve
assert(obj.insync?, "Object is not in sync")
text = File.read(file)
assert_equal(str, text, "Content did not copy correctly")
newstr = "Another string, yo"
obj[:content] = newstr
assert(!obj.insync?, "Object is incorrectly in sync")
assert_events([:file_changed], obj)
text = File.read(file)
assert_equal(newstr, text, "Content did not copy correctly")
obj.retrieve
assert(obj.insync?, "Object is not in sync")
end
# Unfortunately, I know this fails
def disabled_test_recursivemkdir
path = tempfile()
subpath = File.join(path, "this", "is", "a", "dir")
file = nil
assert_nothing_raised {
file = Puppet.type(:file).create(
:name => subpath,
:ensure => "directory",
:recurse => true
)
}
comp = newcomp("yay", file)
comp.finalize
assert_apply(comp)
#assert_events([:directory_created], comp)
assert(FileTest.directory?(subpath), "Did not create directory")
end
# Make sure that content updates the checksum on the same run
def test_checksumchange_for_content
dest = tempfile()
File.open(dest, "w") { |f| f.puts "yayness" }
file = nil
assert_nothing_raised {
file = Puppet.type(:file).create(
:name => dest,
:checksum => "md5",
:content => "This is some content"
)
}
file.retrieve
assert_events([:file_changed], file)
file.retrieve
assert_events([], file)
end
# Make sure that content updates the checksum on the same run
def test_checksumchange_for_ensure
dest = tempfile()
file = nil
assert_nothing_raised {
file = Puppet.type(:file).create(
:name => dest,
:checksum => "md5",
:ensure => "file"
)
}
file.retrieve
assert_events([:file_created], file)
file.retrieve
assert_events([], file)
end
# Make sure that content gets used before ensure
def test_contentbeatsensure
dest = tempfile()
file = nil
assert_nothing_raised {
file = Puppet.type(:file).create(
:name => dest,
:ensure => "file",
:content => "this is some content, yo"
)
}
file.retrieve
assert_events([:file_created], file)
file.retrieve
assert_events([], file)
assert_events([], file)
end
def test_nameandpath
path = tempfile()
file = nil
assert_nothing_raised {
file = Puppet.type(:file).create(
:title => "fileness",
:path => path,
:content => "this is some content"
)
}
assert_apply(file)
assert(FileTest.exists?(path))
end
# Make sure that a missing group isn't fatal at object instantiation time.
def test_missinggroup
file = nil
assert_nothing_raised {
file = Puppet.type(:file).create(
:path => tempfile(),
:group => "fakegroup"
)
}
assert(file.state(:group), "Group state failed")
end
def test_modecreation
path = tempfile()
file = Puppet.type(:file).create(
:path => path,
:ensure => "file",
:mode => "0777"
)
assert_apply(file)
assert_equal(0777, File.stat(path).mode & 007777)
File.unlink(path)
file[:ensure] = "directory"
assert_apply(file)
assert_equal(0777, File.stat(path).mode & 007777)
end
def test_followlinks
basedir = tempfile()
Dir.mkdir(basedir)
file = File.join(basedir, "file")
link = File.join(basedir, "link")
File.open(file, "w", 0644) { |f| f.puts "yayness"; f.flush }
File.symlink(file, link)
obj = nil
assert_nothing_raised {
obj = Puppet.type(:file).create(
:path => link,
:mode => "755"
)
}
obj.retrieve
assert_events([], obj)
# Assert that we default to not following links
assert_equal("%o" % 0644, "%o" % (File.stat(file).mode & 007777))
# Assert that we can manage the link directly, but modes still don't change
obj[:links] = :manage
assert_events([], obj)
assert_equal("%o" % 0644, "%o" % (File.stat(file).mode & 007777))
obj[:links] = :follow
assert_events([:file_changed], obj)
assert_equal("%o" % 0755, "%o" % (File.stat(file).mode & 007777))
# Now verify that content and checksum don't update, either
obj.delete(:mode)
obj[:checksum] = "md5"
obj[:links] = :ignore
assert_events([], obj)
File.open(file, "w") { |f| f.puts "more text" }
assert_events([], obj)
obj[:links] = :follow
assert_events([], obj)
File.open(file, "w") { |f| f.puts "even more text" }
assert_events([:file_changed], obj)
obj.delete(:checksum)
obj[:content] = "this is some content"
obj[:links] = :ignore
assert_events([], obj)
File.open(file, "w") { |f| f.puts "more text" }
assert_events([], obj)
obj[:links] = :follow
assert_events([:file_changed], obj)
end
# If both 'ensure' and 'content' are used, make sure that all of the other
# states are handled correctly.
def test_contentwithmode
path = tempfile()
file = nil
assert_nothing_raised {
file = Puppet.type(:file).create(
:path => path,
:ensure => "file",
:content => "some text\n",
:mode => 0755
)
}
assert_apply(file)
assert_equal("%o" % 0755, "%o" % (File.stat(path).mode & 007777))
end
# Make sure we can create symlinks
def test_symlinks
path = tempfile()
link = tempfile()
File.open(path, "w") { |f| f.puts "yay" }
file = nil
assert_nothing_raised {
file = Puppet.type(:file).create(
:title => "somethingelse",
:ensure => path,
:path => link
)
}
assert_events([:link_created], file)
assert(FileTest.symlink?(link), "Link was not created")
assert_equal(path, File.readlink(link), "Link was created incorrectly")
# Make sure running it again works
assert_events([], file)
assert_events([], file)
assert_events([], file)
end
def test_linkrecurse
dest = tempfile()
link = @file.create :path => tempfile(), :recurse => true, :ensure => dest
ret = nil
# Start with nothing, just to make sure we get nothing back
assert_nothing_raised { ret = link.linkrecurse(true) }
assert_nil(ret, "got a return when the dest doesn't exist")
# then with a directory with only one file
Dir.mkdir(dest)
one = File.join(dest, "one")
File.open(one, "w") { |f| f.puts "" }
link[:ensure] = dest
assert_nothing_raised { ret = link.linkrecurse(true) }
assert_equal(:directory, link.should(:ensure), "ensure was not set to directory")
assert_equal([File.join(link.title, "one")], ret.collect { |f| f.title },
"Did not get linked file")
oneobj = @file[File.join(link.title, "one")]
assert_equal(one, oneobj.should(:target), "target was not set correctly")
oneobj.remove
File.unlink(one)
# Then make sure we get multiple files
returns = []
5.times do |i|
path = File.join(dest, i.to_s)
returns << File.join(link.title, i.to_s)
File.open(path, "w") { |f| f.puts "" }
end
assert_nothing_raised { ret = link.linkrecurse(true) }
assert_equal(returns.sort, ret.collect { |f| f.title },
"Did not get links back")
returns.each do |path|
obj = @file[path]
assert(path, "did not get obj for %s" % path)
sdest = File.join(dest, File.basename(path))
assert_equal(sdest, obj.should(:target),
"target was not set correctly for %s" % path)
end
end
def test_simplerecursivelinking
source = tempfile()
path = tempfile()
subdir = File.join(source, "subdir")
file = File.join(subdir, "file")
system("mkdir -p %s" % subdir)
system("touch %s" % file)
link = nil
assert_nothing_raised {
link = Puppet.type(:file).create(
:ensure => source,
:path => path,
:recurse => true
)
}
assert_apply(link)
sublink = File.join(path, "subdir")
linkpath = File.join(sublink, "file")
assert(File.directory?(path), "dest is not a dir")
assert(File.directory?(sublink), "subdest is not a dir")
assert(File.symlink?(linkpath), "path is not a link")
assert_equal(file, File.readlink(linkpath))
assert_nil(@file[sublink], "objects were not removed")
assert_events([], link)
end
def test_recursivelinking
source = tempfile()
dest = tempfile()
files = []
dirs = []
# Make a bunch of files and dirs
Dir.mkdir(source)
Dir.chdir(source) do
system("mkdir -p %s" % "some/path/of/dirs")
system("mkdir -p %s" % "other/path/of/dirs")
system("touch %s" % "file")
system("touch %s" % "other/file")
system("touch %s" % "some/path/of/file")
system("touch %s" % "some/path/of/dirs/file")
system("touch %s" % "other/path/of/file")
files = %x{find . -type f}.chomp.split(/\n/)
dirs = %x{find . -type d}.chomp.split(/\n/).reject{|d| d =~ /^\.+$/ }
end
link = nil
assert_nothing_raised {
link = Puppet.type(:file).create(
:ensure => source,
:path => dest,
:recurse => true
)
}
assert_apply(link)
files.each do |f|
f.sub!(/^\.#{File::SEPARATOR}/, '')
path = File.join(dest, f)
assert(FileTest.exists?(path), "Link %s was not created" % path)
assert(FileTest.symlink?(path), "%s is not a link" % f)
target = File.readlink(path)
assert_equal(File.join(source, f), target)
end
dirs.each do |d|
d.sub!(/^\.#{File::SEPARATOR}/, '')
path = File.join(dest, d)
assert(FileTest.exists?(path), "Dir %s was not created" % path)
assert(FileTest.directory?(path), "%s is not a directory" % d)
end
end
def test_localrelativelinks
dir = tempfile()
Dir.mkdir(dir)
source = File.join(dir, "source")
File.open(source, "w") { |f| f.puts "yay" }
dest = File.join(dir, "link")
link = nil
assert_nothing_raised {
link = Puppet.type(:file).create(
:path => dest,
:ensure => "source"
)
}
assert_events([:link_created], link)
assert(FileTest.symlink?(dest), "Did not create link")
assert_equal("source", File.readlink(dest))
assert_equal("yay\n", File.read(dest))
end
def test_recursivelinkingmissingtarget
source = tempfile()
dest = tempfile()
objects = []
objects << Puppet.type(:exec).create(
:command => "mkdir %s; touch %s/file" % [source, source],
:title => "yay",
:path => ENV["PATH"]
)
objects << Puppet.type(:file).create(
:ensure => source,
:path => dest,
:recurse => true,
:require => objects[0]
)
assert_apply(*objects)
link = File.join(dest, "file")
assert(FileTest.symlink?(link), "Did not make link")
assert_equal(File.join(source, "file"), File.readlink(link))
end
def test_backupmodes
file = tempfile()
newfile = tempfile()
File.open(file, "w", 0411) { |f| f.puts "yayness" }
obj = nil
assert_nothing_raised {
obj = Puppet::Type.type(:file).create(
:path => file, :content => "rahness\n"
)
}
# user = group = nil
# if Process.uid == 0
# user = nonrootuser
# group = nonrootgroup
# obj[:owner] = user.name
# obj[:group] = group.name
# File.chown(user.uid, group.gid, file)
# end
assert_apply(obj)
backupfile = file + obj[:backup]
@@tmpfiles << backupfile
assert(FileTest.exists?(backupfile),
"Backup file %s does not exist" % backupfile)
assert_equal(0411, filemode(backupfile),
"File mode is wrong for backupfile")
# if Process.uid == 0
# assert_equal(user.uid, File.stat(backupfile).uid)
# assert_equal(group.gid, File.stat(backupfile).gid)
# end
bucket = "bucket"
bpath = tempfile()
Dir.mkdir(bpath)
Puppet::Type.type(:filebucket).create(
:title => bucket, :path => bpath
)
obj[:backup] = bucket
obj[:content] = "New content"
assert_apply(obj)
bucketedpath = File.join(bpath, "18cc17fa3047fcc691fdf49c0a7f539a", "contents")
assert_equal(0440, filemode(bucketedpath))
end
def test_largefilechanges
source = tempfile()
dest = tempfile()
# Now make a large file
File.open(source, "w") { |f|
500.times { |i| f.puts "line %s" % i }
}
obj = Puppet::Type.type(:file).create(
:title => dest, :source => source
)
assert_events([:file_created], obj)
File.open(source, File::APPEND|File::WRONLY) { |f| f.puts "another line" }
assert_events([:file_changed], obj)
# Now modify the dest file
File.open(dest, File::APPEND|File::WRONLY) { |f| f.puts "one more line" }
assert_events([:file_changed, :file_changed], obj)
end
def test_replacefilewithlink
path = tempfile()
link = tempfile()
File.open(path, "w") { |f| f.puts "yay" }
File.open(link, "w") { |f| f.puts "a file" }
file = nil
assert_nothing_raised {
file = Puppet.type(:file).create(
:ensure => path,
:path => link
)
}
assert_events([:link_created], file)
assert(FileTest.symlink?(link), "Link was not created")
assert_equal(path, File.readlink(link), "Link was created incorrectly")
end
def test_replacedirwithlink
path = tempfile()
link = tempfile()
File.open(path, "w") { |f| f.puts "yay" }
Dir.mkdir(link)
File.open(File.join(link, "yay"), "w") do |f| f.puts "boo" end
file = nil
assert_nothing_raised {
file = Puppet.type(:file).create(
:ensure => path,
:path => link,
:backup => false
)
}
# First run through without :force
assert_events([], file)
assert(FileTest.directory?(link), "Link replaced dir without force")
assert_nothing_raised { file[:force] = true }
assert_events([:link_created], file)
assert(FileTest.symlink?(link), "Link was not created")
assert_equal(path, File.readlink(link), "Link was created incorrectly")
end
def test_replace_links_with_files
base = tempfile()
Dir.mkdir(base)
file = File.join(base, "file")
link = File.join(base, "link")
File.open(file, "w") { |f| f.puts "yayness" }
File.symlink(file, link)
obj = Puppet::Type.type(:file).create(
:path => link,
:ensure => "file"
)
assert_apply(obj)
assert_equal("yayness\n", File.read(file),
"Original file got changed")
assert_equal("file", File.lstat(link).ftype, "File is still a link")
end
def test_no_erase_linkedto_files
base = tempfile()
Dir.mkdir(base)
dirs = {}
%w{other source target}.each do |d|
dirs[d] = File.join(base, d)
Dir.mkdir(dirs[d])
end
file = File.join(dirs["other"], "file")
sourcefile = File.join(dirs["source"], "sourcefile")
link = File.join(dirs["target"], "link")
File.open(file, "w") { |f| f.puts "other" }
File.open(sourcefile, "w") { |f| f.puts "source" }
File.symlink(file, link)
obj = Puppet::Type.type(:file).create(
:path => dirs["target"],
:ensure => "file",
:source => dirs["source"],
:recurse => true
)
trans = assert_events([:file_created, :file_created], obj)
newfile = File.join(dirs["target"], "sourcefile")
assert(File.exists?(newfile), "File did not get copied")
assert_equal(File.read(sourcefile), File.read(newfile),
"File did not get copied correctly.")
assert_equal("other\n", File.read(file),
"Original file got changed")
assert_equal("file", File.lstat(link).ftype, "File is still a link")
end
def test_replace_links
dest = tempfile()
otherdest = tempfile()
link = tempfile()
File.open(dest, "w") { |f| f.puts "boo" }
File.open(otherdest, "w") { |f| f.puts "yay" }
obj = Puppet::Type.type(:file).create(
:path => link,
:ensure => otherdest
)
assert_apply(obj)
assert_equal(otherdest, File.readlink(link), "Link did not get created")
obj[:ensure] = dest
assert_apply(obj)
assert_equal(dest, File.readlink(link), "Link did not get changed")
end
def test_file_with_spaces
dir = tempfile()
Dir.mkdir(dir)
source = File.join(dir, "file spaces")
dest = File.join(dir, "another space")
File.open(source, "w") { |f| f.puts :yay }
obj = Puppet::Type.type(:file).create(
:path => dest,
:source => source
)
assert(obj, "Did not create file")
assert_apply(obj)
assert(FileTest.exists?(dest), "File did not get created")
end
def test_present_matches_anything
path = tempfile()
file = Puppet::Type.newfile(:path => path, :ensure => :present)
file.retrieve
assert(! file.insync?, "File incorrectly in sync")
# Now make a file
File.open(path, "w") { |f| f.puts "yay" }
file.retrieve
assert(file.insync?, "File not in sync")
# Now make a directory
File.unlink(path)
Dir.mkdir(path)
file.retrieve
assert(file.insync?, "Directory not considered 'present'")
Dir.rmdir(path)
# Now make a link
file[:links] = :manage
otherfile = tempfile()
File.symlink(otherfile, path)
file.retrieve
assert(file.insync?, "Symlink not considered 'present'")
File.unlink(path)
# Now set some content, and make sure it works
file[:content] = "yayness"
assert_apply(file)
assert_equal("yayness", File.read(path), "Content did not get set correctly")
end
# Make sure unmanaged files are be purged.
def test_purge
sourcedir = tempfile()
destdir = tempfile()
Dir.mkdir(sourcedir)
Dir.mkdir(destdir)
sourcefile = File.join(sourcedir, "sourcefile")
dsourcefile = File.join(destdir, "sourcefile")
localfile = File.join(destdir, "localfile")
randfile = File.join(destdir, "random")
File.open(sourcefile, "w") { |f| f.puts "funtest" }
# this file should get removed
File.open(randfile, "w") { |f| f.puts "footest" }
lfobj = Puppet::Type.newfile(:path => localfile, :content => "rahtest")
destobj = Puppet::Type.newfile(:path => destdir,
:source => sourcedir,
:recurse => true)
assert_apply(lfobj, destobj)
assert(FileTest.exists?(dsourcefile), "File did not get copied")
assert(FileTest.exists?(localfile), "File did not get created")
assert(FileTest.exists?(randfile), "File got prematurely purged")
assert_nothing_raised { destobj[:purge] = true }
assert_apply(lfobj, destobj)
assert(FileTest.exists?(dsourcefile), "File got purged")
assert(FileTest.exists?(localfile), "File got purged")
assert(! FileTest.exists?(randfile), "File did not get purged")
end
# Testing #274. Make sure target can be used without 'ensure'.
def test_target_without_ensure
source = tempfile()
dest = tempfile()
File.open(source, "w") { |f| f.puts "funtest" }
obj = nil
assert_nothing_raised {
obj = Puppet::Type.newfile(:path => dest, :target => source)
}
assert_apply(obj)
end
def test_autorequire_owner_and_group
file = tempfile()
comp = nil
user = nil
group =nil
home = nil
ogroup = nil
assert_nothing_raised {
user = Puppet.type(:user).create(
:name => "pptestu",
:home => file,
:gid => "pptestg"
)
home = Puppet.type(:file).create(
:path => file,
:owner => "pptestu",
:group => "pptestg",
:ensure => "directory"
)
group = Puppet.type(:group).create(
:name => "pptestg"
)
comp = newcomp(user, group, home)
}
- comp.finalize
- comp.retrieve
-
- assert(home.requires?(user), "File did not require owner")
- assert(home.requires?(group), "File did not require group")
+
+ # Now make sure we get a relationship for each of these
+ rels = nil
+ assert_nothing_raised { rels = home.autorequire }
+ assert(rels.detect { |e| e.source == user }, "owner was not autorequired")
+ assert(rels.detect { |e| e.source == group }, "group was not autorequired")
end
# Testing #309 -- //my/file => /my/file
def test_slash_deduplication
["/my/////file/for//testing", "//my/file/for/testing///",
"/my/file/for/testing"].each do |path|
file = nil
assert_nothing_raised do
file = Puppet::Type.newfile(:path => path)
end
assert_equal("/my/file/for/testing", file.title)
assert_equal(file, Puppet::Type.type(:file)["/my/file/for/testing"])
Puppet::Type.type(:file).clear
end
end
# Testing #304
def test_links_to_directories
link = tempfile()
file = tempfile()
dir = tempfile()
Dir.mkdir(dir)
bucket = Puppet::Type.newfilebucket :name => "main"
File.symlink(dir, link)
File.open(file, "w") { |f| f.puts "" }
assert_equal(dir, File.readlink(link))
obj = Puppet::Type.newfile :path => link, :ensure => :link,
:target => file, :recurse => false, :backup => "main"
assert_apply(obj)
assert_equal(file, File.readlink(link))
end
# Testing #303
def test_nobackups_with_links
link = tempfile()
new = tempfile()
File.open(link, "w") { |f| f.puts "old" }
File.open(new, "w") { |f| f.puts "new" }
obj = Puppet::Type.newfile :path => link, :ensure => :link,
:target => new, :recurse => true, :backup => false
assert_nothing_raised do
obj.handlebackup
end
bfile = [link, "puppet-bak"].join(".")
assert(! FileTest.exists?(bfile), "Backed up when told not to")
assert_apply(obj)
assert(! FileTest.exists?(bfile), "Backed up when told not to")
end
# Make sure we consistently handle backups for all cases.
def test_ensure_with_backups
# We've got three file types, so make sure we can replace any type
# with the other type and that backups are done correctly.
types = [:file, :directory, :link]
dir = tempfile()
path = File.join(dir, "test")
linkdest = tempfile()
creators = {
:file => proc { File.open(path, "w") { |f| f.puts "initial" } },
:directory => proc { Dir.mkdir(path) },
:link => proc { File.symlink(linkdest, path) }
}
bucket = Puppet::Type.newfilebucket :name => "main", :path => tempfile()
obj = Puppet::Type.newfile :path => path, :force => true,
:links => :manage
Puppet[:trace] = true
["main", false].each do |backup|
obj[:backup] = backup
obj.finish
types.each do |should|
types.each do |is|
# It makes no sense to replace a directory with a directory
# next if should == :directory and is == :directory
Dir.mkdir(dir)
# Make the thing
creators[is].call
obj[:ensure] = should
if should == :link
obj[:target] = linkdest
else
if obj.state(:target)
obj.delete(:target)
end
end
# First try just removing the initial data
assert_nothing_raised do
obj.remove_existing(should)
end
unless is == should
# Make sure the original is gone
assert(! FileTest.exists?(obj[:path]),
"remove_existing did not work: " +
"did not remove %s with %s" % [is, should])
end
FileUtils.rmtree(obj[:path])
# Now make it again
creators[is].call
state = obj.state(:ensure)
state.retrieve
unless state.insync?
assert_nothing_raised do
state.sync
end
end
FileUtils.rmtree(dir)
end
end
end
end
-
- def test_check_checksums
- dir = tempfile()
- Dir.mkdir(dir)
- subdir = File.join(dir, "sub")
- Dir.mkdir(subdir)
- file = File.join(dir, "file")
- File.open(file, "w") { |f| f.puts "yay" }
-
- obj = Puppet::Type.type(:file).create(
- :path => dir, :check => :checksum, :recurse => true
- )
-
- assert_apply(obj)
- File.open(file, "w") { |f| f.puts "rah" }
- sleep 1
- system("touch %s" % subdir)
- Puppet::Storage.store
- Puppet::Storage.load
- assert_apply(obj)
- [file, subdir].each do |path|
- sub = Puppet::Type.type(:file)[path]
- assert(sub, "did not find obj for %s" % path)
- sub.retrieve
-
- assert_nothing_raised do
- sub.state(:checksum).sync
- end
- end
- end
end
# $Id$
diff --git a/test/types/filesources.rb b/test/types/filesources.rb
index bae4c7d5f..c1c601b59 100755
--- a/test/types/filesources.rb
+++ b/test/types/filesources.rb
@@ -1,939 +1,940 @@
#!/usr/bin/env ruby
$:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/
require 'puppet'
require 'cgi'
require 'fileutils'
require 'puppettest'
class TestFileSources < Test::Unit::TestCase
include PuppetTest::FileTesting
def setup
super
if defined? @port
@port += 1
else
@port = 8800
end
@file = Puppet::Type.type(:file)
end
def use_storage
begin
initstorage
rescue
system("rm -rf %s" % Puppet[:statefile])
end
end
def initstorage
Puppet::Storage.init
Puppet::Storage.load
end
# Make a simple recursive tree.
def mk_sourcetree
source = tempfile()
sourcefile = File.join(source, "file")
Dir.mkdir source
File.open(sourcefile, "w") { |f| f.puts "yay" }
dest = tempfile()
destfile = File.join(dest, "file")
return source, dest, sourcefile, destfile
end
def test_newchild
path = tempfile()
@@tmpfiles.push path
FileUtils.mkdir_p path
File.open(File.join(path,"childtest"), "w") { |of|
of.puts "yayness"
}
file = nil
comp = nil
trans = nil
assert_nothing_raised {
file = Puppet.type(:file).create(
:name => path
)
}
child = nil
assert_nothing_raised {
child = file.newchild("childtest", true)
}
assert(child)
assert_raise(Puppet::DevError) {
file.newchild(File.join(path,"childtest"), true)
}
end
def test_describe
source = tempfile()
dest = tempfile()
file = Puppet::Type.newfile :path => dest, :source => source,
:title => "copier"
state = file.state(:source)
# First try describing with a normal source
result = nil
assert_nothing_raised do
result = state.describe(source)
end
assert_nil(result, "Got a result back when source is missing")
# Now make a remote directory
Dir.mkdir(source)
assert_nothing_raised do
result = state.describe(source)
end
assert_equal("directory", result[:type])
# And as a file
Dir.rmdir(source)
File.open(source, "w") { |f| f.puts "yay" }
assert_nothing_raised do
result = state.describe(source)
end
assert_equal("file", result[:type])
assert(result[:checksum], "did not get value for checksum")
if Puppet::SUIDManager.uid == 0
assert(result.has_key?("owner"), "Lost owner in describe")
else
assert(! result.has_key?("owner"),
"Kept owner in describe even tho not root")
end
# Now let's do the various link things
File.unlink(source)
target = tempfile()
File.open(target, "w") { |f| f.puts "yay" }
File.symlink(target, source)
file[:links] = :ignore
assert_nil(state.describe(source),
"Links were not ignored")
file[:links] = :manage
# We can't manage links at this point
assert_raise(Puppet::FileServerError) do
state.describe(source)
end
# And then make sure links get followed, otherwise
file[:links] = :follow
assert_equal("file", state.describe(source)[:type])
end
def test_source_retrieve
source = tempfile()
dest = tempfile()
file = Puppet::Type.newfile :path => dest, :source => source,
:title => "copier"
assert(file.state(:checksum), "source state did not create checksum state")
state = file.state(:source)
assert(state, "did not get source state")
# Make sure the munge didn't actually change the source
- assert_equal(source, state.should, "munging changed the source")
+ assert_equal([source], state.should, "munging changed the source")
# First try it with a missing source
assert_nothing_raised do
state.retrieve
end
# And make sure the state considers itself in sync, since there's nothing
# to do
assert(state.insync?, "source thinks there's work to do with no file or dest")
# Now make the dest a directory, and make sure the object sets :ensure up to
# create a directory
Dir.mkdir(source)
assert_nothing_raised do
state.retrieve
end
assert_equal(:directory, file.should(:ensure),
"Did not set to create directory")
# And make sure the source state won't try to do anything with a remote dir
assert(state.insync?, "Source was out of sync even tho remote is dir")
# Now remove the source, and make sure :ensure was not modified
Dir.rmdir(source)
assert_nothing_raised do
state.retrieve
end
assert_equal(:directory, file.should(:ensure),
"Did not keep :ensure setting")
# Now have a remote file and make sure things work correctly
File.open(source, "w") { |f| f.puts "yay" }
File.chmod(0755, source)
assert_nothing_raised do
state.retrieve
end
assert_equal(:file, file.should(:ensure),
"Did not make correct :ensure setting")
assert_equal(0755, file.should(:mode),
"Mode was not copied over")
# Now let's make sure that we get the first found source
fake = tempfile()
state.should = [fake, source]
assert_nothing_raised do
state.retrieve
end
assert_equal(Digest::MD5.hexdigest(File.read(source)), state.checksum.sub(/^\{\w+\}/, ''),
"Did not catch later source")
end
def test_insync
source = tempfile()
dest = tempfile()
file = Puppet::Type.newfile :path => dest, :source => source,
:title => "copier"
state = file.state(:source)
assert(state, "did not get source state")
# Try it with no source at all
file.retrieve
assert(state.insync?, "source state not in sync with missing source")
# with a directory
Dir.mkdir(source)
file.retrieve
assert(state.insync?, "source state not in sync with directory as source")
Dir.rmdir(source)
# with a file
File.open(source, "w") { |f| f.puts "yay" }
file.retrieve
assert(!state.insync?, "source state was in sync when file was missing")
# With a different file
File.open(dest, "w") { |f| f.puts "foo" }
file.retrieve
assert(!state.insync?, "source state was in sync with different file")
# with matching files
File.open(dest, "w") { |f| f.puts "yay" }
file.retrieve
assert(state.insync?, "source state was not in sync with matching file")
end
def test_source_sync
source = tempfile()
dest = tempfile()
file = Puppet::Type.newfile :path => dest, :source => source,
:title => "copier"
state = file.state(:source)
File.open(source, "w") { |f| f.puts "yay" }
file.retrieve
assert(! state.insync?, "source thinks it's in sync")
event = nil
assert_nothing_raised do
event = state.sync
end
assert_equal(:file_created, event)
assert_equal(File.read(source), File.read(dest),
"File was not copied correctly")
# Now write something different
File.open(source, "w") { |f| f.puts "rah" }
file.retrieve
assert(! state.insync?, "source should be out of sync")
assert_nothing_raised do
event = state.sync
end
assert_equal(:file_changed, event)
assert_equal(File.read(source), File.read(dest),
"File was not copied correctly")
end
# XXX This test doesn't cover everything. Specifically,
# it doesn't handle 'ignore' and 'links'.
def test_sourcerecurse
source, dest, sourcefile, destfile = mk_sourcetree
# The sourcerecurse method will only ever get called when we're
# recursing, so we go ahead and set it.
obj = Puppet::Type.newfile :source => source, :path => dest, :recurse => true
result = nil
assert_nothing_raised do
result = obj.sourcerecurse(true)
end
dfileobj = @file[destfile]
assert(dfileobj, "Did not create destfile object")
assert_equal([dfileobj], result)
# Clean this up so it can be recreated
dfileobj.remove
# Make sure we correctly iterate over the sources
nosource = tempfile()
obj[:source] = [nosource, source]
result = nil
assert_nothing_raised do
result = obj.sourcerecurse(true)
end
dfileobj = @file[destfile]
assert(dfileobj, "Did not create destfile object with a missing source")
assert_equal([dfileobj], result)
dfileobj.remove
# Lastly, make sure we return an empty array when no sources are there
obj[:source] = [nosource, tempfile()]
assert_nothing_raised do
result = obj.sourcerecurse(true)
end
assert_equal([], result, "Sourcerecurse failed when all sources are missing")
end
def test_simplelocalsource
path = tempfile()
FileUtils.mkdir_p path
frompath = File.join(path,"source")
topath = File.join(path,"dest")
fromfile = nil
tofile = nil
trans = nil
File.open(frompath, File::WRONLY|File::CREAT|File::APPEND) { |of|
of.puts "yayness"
}
assert_nothing_raised {
tofile = Puppet.type(:file).create(
:name => topath,
:source => frompath
)
}
assert_apply(tofile)
assert(FileTest.exists?(topath), "File #{topath} is missing")
from = File.open(frompath) { |o| o.read }
to = File.open(topath) { |o| o.read }
assert_equal(from,to)
end
# Make sure a simple recursive copy works
def test_simple_recursive_source
source, dest, sourcefile, destfile = mk_sourcetree
file = Puppet::Type.newfile :path => dest, :source => source, :recurse => true
assert_events([:directory_created, :file_created], file)
assert(FileTest.directory?(dest), "Dest dir was not created")
assert(FileTest.file?(destfile), "dest file was not created")
assert_equal("yay\n", File.read(destfile), "dest file was not copied correctly")
end
def recursive_source_test(fromdir, todir)
Puppet::Type.allclear
initstorage
tofile = nil
trans = nil
assert_nothing_raised {
tofile = Puppet.type(:file).create(
:path => todir,
:recurse => true,
:backup => false,
:source => fromdir
)
}
assert_apply(tofile)
assert(FileTest.exists?(todir), "Created dir %s does not exist" % todir)
Puppet::Type.allclear
end
def run_complex_sources(networked = false)
path = tempfile()
# first create the source directory
FileUtils.mkdir_p path
# okay, let's create a directory structure
fromdir = File.join(path,"fromdir")
Dir.mkdir(fromdir)
FileUtils.cd(fromdir) {
File.open("one", "w") { |f| f.puts "onefile"}
File.open("two", "w") { |f| f.puts "twofile"}
}
todir = File.join(path, "todir")
source = fromdir
if networked
source = "puppet://localhost/%s%s" % [networked, fromdir]
end
recursive_source_test(source, todir)
return [fromdir,todir, File.join(todir, "one"), File.join(todir, "two")]
end
def test_complex_sources_twice
fromdir, todir, one, two = run_complex_sources
assert_trees_equal(fromdir,todir)
recursive_source_test(fromdir, todir)
assert_trees_equal(fromdir,todir)
# Now remove the whole tree and try it again.
[one, two].each do |f| File.unlink(f) end
Dir.rmdir(todir)
recursive_source_test(fromdir, todir)
assert_trees_equal(fromdir,todir)
end
def test_sources_with_deleted_destfiles
fromdir, todir, one, two = run_complex_sources
assert(FileTest.exists?(todir))
# We shouldn't have a 'two' file object in memory
assert_nil(@file[two], "object for 'two' is still in memory")
# then delete a file
File.unlink(two)
- puts "yay"
# and run
recursive_source_test(fromdir, todir)
assert(FileTest.exists?(two), "Deleted file was not recopied")
# and make sure they're still equal
assert_trees_equal(fromdir,todir)
end
def test_sources_with_readonly_destfiles
- fromdir, todir = run_complex_sources
+ fromdir, todir, one, two = run_complex_sources
assert(FileTest.exists?(todir))
- readonly_random_files(todir)
+ File.chmod(0600, one)
+ recursive_source_test(fromdir, todir)
+
+ # and make sure they're still equal
+ assert_trees_equal(fromdir,todir)
+
+ # Now try it with the directory being read-only
+ File.chmod(0111, todir)
recursive_source_test(fromdir, todir)
# and make sure they're still equal
assert_trees_equal(fromdir,todir)
end
def test_sources_with_modified_dest_files
- fromdir, todir = run_complex_sources
+ fromdir, todir, one, two = run_complex_sources
assert(FileTest.exists?(todir))
- # then modify some files
- modify_random_files(todir)
+
+ # Modify a dest file
+ File.open(two, "w") { |f| f.puts "something else" }
recursive_source_test(fromdir, todir)
# and make sure they're still equal
assert_trees_equal(fromdir,todir)
end
def test_sources_with_added_destfiles
fromdir, todir = run_complex_sources
assert(FileTest.exists?(todir))
# and finally, add some new files
add_random_files(todir)
recursive_source_test(fromdir, todir)
fromtree = file_list(fromdir)
totree = file_list(todir)
assert(fromtree != totree, "Trees are incorrectly equal")
# then remove our new files
FileUtils.cd(todir) {
%x{find . 2>/dev/null}.chomp.split(/\n/).each { |file|
if file =~ /file[0-9]+/
File.unlink(file)
end
}
}
# and make sure they're still equal
assert_trees_equal(fromdir,todir)
end
+ # Make sure added files get correctly caught during recursion
def test_RecursionWithAddedFiles
basedir = tempfile()
Dir.mkdir(basedir)
@@tmpfiles << basedir
file1 = File.join(basedir, "file1")
file2 = File.join(basedir, "file2")
subdir1 = File.join(basedir, "subdir1")
file3 = File.join(subdir1, "file")
- File.open(file1, "w") { |f| 3.times { f.print rand(100) } }
+ File.open(file1, "w") { |f| f.puts "yay" }
rootobj = nil
assert_nothing_raised {
rootobj = Puppet.type(:file).create(
:name => basedir,
:recurse => true,
- :check => %w{type owner}
+ :check => %w{type owner},
+ :mode => 0755
)
-
- rootobj.evaluate
}
+
+ assert_apply(rootobj)
+ assert_equal(0755, filemode(file1))
- klass = Puppet.type(:file)
- assert(klass[basedir])
- assert(klass[file1])
- assert_nil(klass[file2])
-
- File.open(file2, "w") { |f| 3.times { f.print rand(100) } }
-
- assert_nothing_raised {
- rootobj.evaluate
- }
- assert(klass[file2])
+ File.open(file2, "w") { |f| f.puts "rah" }
+ assert_apply(rootobj)
+ assert_equal(0755, filemode(file2))
Dir.mkdir(subdir1)
- File.open(file3, "w") { |f| 3.times { f.print rand(100) } }
-
- assert_nothing_raised {
- rootobj.evaluate
- }
- assert(klass[file3])
+ File.open(file3, "w") { |f| f.puts "foo" }
+ assert_apply(rootobj)
+ assert_equal(0755, filemode(file3))
end
def mkfileserverconf(mounts)
file = tempfile()
File.open(file, "w") { |f|
mounts.each { |path, name|
f.puts "[#{name}]\n\tpath #{path}\n\tallow *\n"
}
}
@@tmpfiles << file
return file
end
def test_NetworkSources
server = nil
mounts = {
"/" => "root"
}
fileserverconf = mkfileserverconf(mounts)
Puppet[:autosign] = true
Puppet[:masterport] = 8762
serverpid = nil
assert_nothing_raised() {
server = Puppet::Server.new(
:Handlers => {
:CA => {}, # so that certs autogenerate
:FileServer => {
:Config => fileserverconf
}
}
)
}
serverpid = fork {
assert_nothing_raised() {
#trap(:INT) { server.shutdown; Kernel.exit! }
trap(:INT) { server.shutdown }
server.start
}
}
@@tmppids << serverpid
sleep(1)
fromdir, todir = run_complex_sources("root")
assert_trees_equal(fromdir,todir)
recursive_source_test(fromdir, todir)
assert_trees_equal(fromdir,todir)
assert_nothing_raised {
system("kill -INT %s" % serverpid)
}
end
def test_networkSourcesWithoutService
server = nil
Puppet[:autosign] = true
Puppet[:masterport] = 8765
serverpid = nil
assert_nothing_raised() {
server = Puppet::Server.new(
:Handlers => {
:CA => {}, # so that certs autogenerate
}
)
}
serverpid = fork {
assert_nothing_raised() {
#trap(:INT) { server.shutdown; Kernel.exit! }
trap(:INT) { server.shutdown }
server.start
}
}
@@tmppids << serverpid
sleep(1)
name = File.join(tmpdir(), "nosourcefile")
file = Puppet.type(:file).create(
:source => "puppet://localhost/dist/file",
:name => name
)
assert_nothing_raised {
file.retrieve
}
comp = newcomp("nosource", file)
assert_nothing_raised {
comp.evaluate
}
assert(!FileTest.exists?(name), "File with no source exists anyway")
end
def test_unmountedNetworkSources
server = nil
mounts = {
"/" => "root",
"/noexistokay" => "noexist"
}
fileserverconf = mkfileserverconf(mounts)
Puppet[:autosign] = true
Puppet[:masterport] = @port
serverpid = nil
assert_nothing_raised() {
server = Puppet::Server.new(
:Port => @port,
:Handlers => {
:CA => {}, # so that certs autogenerate
:FileServer => {
:Config => fileserverconf
}
}
)
}
serverpid = fork {
assert_nothing_raised() {
#trap(:INT) { server.shutdown; Kernel.exit! }
trap(:INT) { server.shutdown }
server.start
}
}
@@tmppids << serverpid
sleep(1)
name = File.join(tmpdir(), "nosourcefile")
file = Puppet.type(:file).create(
:source => "puppet://localhost/noexist/file",
:name => name
)
assert_nothing_raised {
file.retrieve
}
comp = newcomp("nosource", file)
assert_nothing_raised {
comp.evaluate
}
assert(!FileTest.exists?(name), "File with no source exists anyway")
end
def test_alwayschecksum
from = tempfile()
to = tempfile()
File.open(from, "w") { |f| f.puts "yayness" }
File.open(to, "w") { |f| f.puts "yayness" }
file = nil
# Now the files should be exactly the same, so we should not see attempts
# at copying
assert_nothing_raised {
file = Puppet.type(:file).create(
:path => to,
:source => from
)
}
file.retrieve
assert(file.is(:checksum), "File does not have a checksum state")
assert_equal(0, file.evaluate.length, "File produced changes")
end
def test_sourcepaths
files = []
3.times {
files << tempfile()
}
to = tempfile()
File.open(files[-1], "w") { |f| f.puts "yee-haw" }
file = nil
assert_nothing_raised {
file = Puppet.type(:file).create(
:name => to,
:source => files
)
}
comp = newcomp(file)
assert_events([:file_created], comp)
assert(File.exists?(to), "File does not exist")
txt = nil
File.open(to) { |f| txt = f.read.chomp }
assert_equal("yee-haw", txt, "Contents do not match")
end
# Make sure that source-copying updates the checksum on the same run
def test_checksumchange
source = tempfile()
dest = tempfile()
File.open(dest, "w") { |f| f.puts "boo" }
File.open(source, "w") { |f| f.puts "yay" }
file = nil
assert_nothing_raised {
file = Puppet.type(:file).create(
:name => dest,
:source => source
)
}
file.retrieve
assert_events([:file_changed], file)
file.retrieve
assert_events([], file)
end
# Make sure that source-copying updates the checksum on the same run
def test_sourcebeatsensure
source = tempfile()
dest = tempfile()
File.open(source, "w") { |f| f.puts "yay" }
file = nil
assert_nothing_raised {
file = Puppet.type(:file).create(
:name => dest,
:ensure => "file",
:source => source
)
}
file.retrieve
assert_events([:file_created], file)
file.retrieve
assert_events([], file)
assert_events([], file)
end
def test_sourcewithlinks
source = tempfile()
link = tempfile()
dest = tempfile()
File.open(source, "w") { |f| f.puts "yay" }
File.symlink(source, link)
file = nil
assert_nothing_raised {
file = Puppet.type(:file).create(
:name => dest,
:source => link
)
}
# Default to skipping links
assert_events([], file)
assert(! FileTest.exists?(dest), "Created link")
# Now follow the links
file[:links] = :follow
assert_events([:file_created], file)
assert(FileTest.file?(dest), "Destination is not a file")
# Now copy the links
#assert_raise(Puppet::FileServerError) {
trans = nil
assert_nothing_raised {
file[:links] = :manage
comp = newcomp(file)
trans = comp.evaluate
trans.evaluate
}
assert(trans.failed?(file), "Object did not fail to copy links")
end
def test_changes
source = tempfile()
dest = tempfile()
File.open(source, "w") { |f| f.puts "yay" }
obj = nil
assert_nothing_raised {
obj = Puppet.type(:file).create(
:name => dest,
:source => source
)
}
assert_events([:file_created], obj)
assert_equal(File.read(source), File.read(dest), "Files are not equal")
assert_events([], obj)
File.open(source, "w") { |f| f.puts "boo" }
assert_events([:file_changed], obj)
assert_equal(File.read(source), File.read(dest), "Files are not equal")
assert_events([], obj)
File.open(dest, "w") { |f| f.puts "kaboom" }
# There are two changes, because first the checksum is noticed, and
# then the source causes a change
assert_events([:file_changed, :file_changed], obj)
assert_equal(File.read(source), File.read(dest), "Files are not equal")
assert_events([], obj)
end
def test_file_source_with_space
dir = tempfile()
source = File.join(dir, "file with spaces")
Dir.mkdir(dir)
File.open(source, "w") { |f| f.puts "yayness" }
newdir = tempfile()
newpath = File.join(newdir, "file with spaces")
file = Puppet::Type.newfile(
:path => newdir,
:source => dir,
:recurse => true
)
assert_apply(file)
assert(FileTest.exists?(newpath), "Did not create file")
assert_equal("yayness\n", File.read(newpath))
end
# Make sure files aren't replaced when replace is false, but otherwise
# are.
def test_replace
source = tempfile()
File.open(source, "w") { |f| f.puts "yayness" }
dest = tempfile()
file = Puppet::Type.newfile(
:path => dest,
:source => source,
:recurse => true
)
assert_apply(file)
assert(FileTest.exists?(dest), "Did not create file")
assert_equal("yayness\n", File.read(dest))
# Now set :replace
assert_nothing_raised {
file[:replace] = false
}
File.open(source, "w") { |f| f.puts "funtest" }
assert_apply(file)
# Make sure it doesn't change.
- assert_equal("yayness\n", File.read(dest))
+ assert_equal("yayness\n", File.read(dest),
+ "File got replaced when :replace was false")
# Now set it to true and make sure it does change.
assert_nothing_raised {
file[:replace] = true
}
assert_apply(file)
# Make sure it doesn't change.
- assert_equal("funtest\n", File.read(dest))
+ assert_equal("funtest\n", File.read(dest),
+ "File was not replaced when :replace was true")
end
# Testing #285. This just makes sure that URI parsing works correctly.
def test_fileswithpoundsigns
dir = tstdir()
subdir = File.join(dir, "#dir")
Dir.mkdir(subdir)
file = File.join(subdir, "file")
File.open(file, "w") { |f| f.puts "yayness" }
dest = tempfile()
source = "file://localhost#{dir}"
obj = Puppet::Type.newfile(
:path => dest,
:source => source,
:recurse => true
)
newfile = File.join(dest, "#dir", "file")
poundsource = "file://localhost#{subdir}"
sourceobj = path = nil
assert_nothing_raised {
sourceobj, path = obj.uri2obj(poundsource)
}
assert_equal("/localhost" + URI.escape(subdir), path)
assert_apply(obj)
assert(FileTest.exists?(newfile), "File did not get created")
assert_equal("yayness\n", File.read(newfile))
end
end
# $Id$
diff --git a/test/types/tidy.rb b/test/types/tidy.rb
index 20694762f..513b05319 100755
--- a/test/types/tidy.rb
+++ b/test/types/tidy.rb
@@ -1,211 +1,211 @@
#!/usr/bin/env ruby
$:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/
require 'puppet'
require 'puppettest'
class TestTidy < Test::Unit::TestCase
include PuppetTest::FileTesting
def mktmpfile
# because luke's home directory is on nfs, it can't be used for testing
# as root
tmpfile = tempfile()
File.open(tmpfile, "w") { |f| f.puts rand(100) }
@@tmpfiles.push tmpfile
return tmpfile
end
def mktmpdir
dir = File.join(tmpdir(), "puppetlinkdir")
unless FileTest.exists?(dir)
Dir.mkdir(dir)
end
@@tmpfiles.push dir
return dir
end
def test_tidydirs
dir = mktmpdir
- file = File.join(dir, "tidytesting")
+ file = File.join(dir, "file")
File.open(file, "w") { |f|
- f.puts rand(100)
+ f.puts "some stuff"
}
tidy = Puppet.type(:tidy).create(
:name => dir,
:size => "1b",
:age => "1s",
:rmdirs => true,
:recurse => true
)
sleep(2)
assert_events([:file_tidied, :file_tidied], tidy)
assert(!FileTest.exists?(file), "Tidied %s still exists" % file)
assert(!FileTest.exists?(dir), "Tidied %s still exists" % dir)
end
def disabled_test_recursion
source = mktmpdir()
FileUtils.cd(source) {
mkranddirsandfiles()
}
link = nil
assert_nothing_raised {
link = newlink(:target => source, :recurse => true)
}
comp = newcomp("linktest",link)
cycle(comp)
path = link.name
list = file_list(path)
FileUtils.cd(path) {
list.each { |file|
unless FileTest.directory?(file)
assert(FileTest.symlink?(file))
target = File.readlink(file)
assert_equal(target,File.join(source,file.sub(/^\.\//,'')))
end
}
}
end
# Test the different age iterations.
def test_age_conversions
tidy = Puppet::Type.newtidy :path => tempfile(), :age => "1m"
convertors = {
:second => 1,
:minute => 60
}
convertors[:hour] = convertors[:minute] * 60
convertors[:day] = convertors[:hour] * 24
convertors[:week] = convertors[:day] * 7
# First make sure we default to days
assert_nothing_raised do
tidy[:age] = "2"
end
assert_equal(2 * convertors[:day], tidy[:age],
"Converted 2 wrong")
convertors.each do |name, number|
init = name.to_s[0..0] # The first letter
[0, 1, 5].each do |multi|
[init, init.upcase].each do |letter|
age = multi.to_s + letter.to_s
assert_nothing_raised do
tidy[:age] = age
end
assert_equal(multi * convertors[name], tidy[:age],
"Converted %s wrong" % age)
end
end
end
end
def test_size_conversions
convertors = {
:b => 0,
:kb => 1,
:mb => 2,
:gb => 3
}
tidy = Puppet::Type.newtidy :path => tempfile(), :age => "1m"
# First make sure we default to kb
assert_nothing_raised do
tidy[:size] = "2"
end
assert_equal(2048, tidy[:size],
"Converted 2 wrong")
convertors.each do |name, number|
init = name.to_s[0..0] # The first letter
[0, 1, 5].each do |multi|
[init, init.upcase].each do |letter|
size = multi.to_s + letter.to_s
assert_nothing_raised do
tidy[:size] = size
end
total = multi
number.times do total *= 1024 end
assert_equal(total, tidy[:size],
"Converted %s wrong" % size)
end
end
end
end
def test_agetest
tidy = Puppet::Type.newtidy :path => tempfile(), :age => "1m"
state = tidy.state(:tidyup)
# Set it to something that should be fine
state.is = [Time.now.to_i - 5, 50]
assert(state.insync?, "Tried to tidy a low age")
# Now to something that should fail
state.is = [Time.now.to_i - 120, 50]
assert(! state.insync?, "Incorrectly skipped tidy")
end
def test_sizetest
tidy = Puppet::Type.newtidy :path => tempfile(), :size => "1k"
state = tidy.state(:tidyup)
# Set it to something that should be fine
state.is = [5, 50]
assert(state.insync?, "Tried to tidy a low size")
# Now to something that should fail
state.is = [120, 2048]
assert(! state.insync?, "Incorrectly skipped tidy")
end
# Make sure we can remove different types of files
def test_tidytypes
path = tempfile()
tidy = Puppet::Type.newtidy :path => path, :size => "1b", :age => "1s"
# Start with a file
File.open(path, "w") { |f| f.puts "this is a test" }
assert_events([:file_tidied], tidy)
assert(! FileTest.exists?(path), "File was not removed")
# Then a link
dest = tempfile
File.open(dest, "w") { |f| f.puts "this is a test" }
File.symlink(dest, path)
assert_events([:file_tidied], tidy)
assert(! FileTest.exists?(path), "Link was not removed")
assert(FileTest.exists?(dest), "Destination was removed")
# And a directory
Dir.mkdir(path)
tidy.is = [:tidyup, [Time.now - 1024, 1]]
tidy[:rmdirs] = true
assert_events([:file_tidied], tidy)
assert(! FileTest.exists?(path), "File was not removed")
end
end
# $Id$
diff --git a/test/types/user.rb b/test/types/user.rb
index a3a2e14f0..958434fa5 100755
--- a/test/types/user.rb
+++ b/test/types/user.rb
@@ -1,449 +1,451 @@
#!/usr/bin/env ruby
$:.unshift("../lib").unshift("../../lib") if __FILE__ =~ /\.rb$/
require 'etc'
require 'puppet/type'
require 'puppettest'
class TestUser < Test::Unit::TestCase
include PuppetTest
p = Puppet::Type.type(:user).provide :fake, :parent => PuppetTest::FakeProvider do
@name = :fake
apimethods
def create
@ensure = :present
@model.eachstate do |state|
next if state.name == :ensure
state.sync
end
end
def delete
@ensure = :absent
@model.eachstate do |state|
send(state.name.to_s + "=", :absent)
end
end
def exists?
if defined? @ensure and @ensure == :present
true
else
false
end
end
end
FakeUserProvider = p
@@fakeproviders[:group] = p
def findshell(old = nil)
%w{/bin/sh /bin/bash /sbin/sh /bin/ksh /bin/zsh /bin/csh /bin/tcsh
/usr/bin/sh /usr/bin/bash /usr/bin/ksh /usr/bin/zsh /usr/bin/csh
/usr/bin/tcsh}.find { |shell|
if old
FileTest.exists?(shell) and shell != old
else
FileTest.exists?(shell)
end
}
end
def setup
super
Puppet::Type.type(:user).defaultprovider = FakeUserProvider
end
def teardown
Puppet::Type.type(:user).defaultprovider = nil
super
end
def mkuser(name)
user = nil
assert_nothing_raised {
user = Puppet.type(:user).create(
:name => name,
:comment => "Puppet Testing User",
:gid => Puppet::SUIDManager.gid,
:shell => findshell(),
:home => "/home/%s" % name
)
}
assert(user, "Did not create user")
return user
end
def attrtest_ensure(user)
old = user.provider.ensure
user[:ensure] = :absent
comp = newcomp("ensuretest", user)
assert_apply(user)
assert(!user.provider.exists?, "User is still present")
user[:ensure] = :present
assert_events([:user_created], comp)
assert(user.provider.exists?, "User is absent")
user[:ensure] = :absent
trans = assert_events([:user_removed], comp)
assert_rollback_events(trans, [:user_created], "user")
user[:ensure] = old
assert_apply(user)
end
def attrtest_comment(user)
user.retrieve
old = user.provider.comment
user[:comment] = "A different comment"
comp = newcomp("commenttest", user)
trans = assert_events([:user_changed], comp, "user")
assert_equal("A different comment", user.provider.comment,
"Comment was not changed")
assert_rollback_events(trans, [:user_changed], "user")
assert_equal(old, user.provider.comment,
"Comment was not reverted")
end
def attrtest_home(user)
obj = nil
comp = newcomp("hometest", user)
old = user.provider.home
user[:home] = old
trans = assert_events([], comp, "user")
user[:home] = "/tmp"
trans = assert_events([:user_changed], comp, "user")
assert_equal("/tmp", user.provider.home, "Home was not changed")
assert_rollback_events(trans, [:user_changed], "user")
assert_equal(old, user.provider.home, "Home was not reverted")
end
def attrtest_shell(user)
old = user.provider.shell
comp = newcomp("shelltest", user)
user[:shell] = old
trans = assert_events([], comp, "user")
newshell = findshell(old)
unless newshell
$stderr.puts "Cannot find alternate shell; skipping shell test"
return
end
user[:shell] = newshell
trans = assert_events([:user_changed], comp, "user")
user.retrieve
assert_equal(newshell, user.provider.shell,
"Shell was not changed")
assert_rollback_events(trans, [:user_changed], "user")
user.retrieve
assert_equal(old, user.provider.shell, "Shell was not reverted")
end
def attrtest_gid(user)
obj = nil
old = user.provider.gid
comp = newcomp("gidtest", user)
user.retrieve
user[:gid] = old
trans = assert_events([], comp, "user")
newgid = %w{nogroup nobody staff users daemon}.find { |gid|
begin
group = Etc.getgrnam(gid)
rescue ArgumentError => detail
next
end
old != group.gid
}
unless newgid
$stderr.puts "Cannot find alternate group; skipping gid test"
return
end
# first test by name
assert_nothing_raised("Failed to specify group by name") {
user[:gid] = newgid
}
trans = assert_events([:user_changed], comp, "user")
# then by id
newgid = Etc.getgrnam(newgid).gid
assert_nothing_raised("Failed to specify group by id") {
user[:gid] = newgid
}
user.retrieve
assert_events([], comp, "user")
assert_equal(newgid, user.provider.gid, "GID was not changed")
assert_rollback_events(trans, [:user_changed], "user")
assert_equal(old, user.provider.gid, "GID was not reverted")
end
def attrtest_uid(user)
obj = nil
comp = newcomp("uidtest", user)
user.provider.uid = 1
old = 1
newuid = 1
while true
newuid += 1
if newuid - old > 1000
$stderr.puts "Could not find extra test UID"
return
end
begin
newuser = Etc.getpwuid(newuid)
rescue ArgumentError => detail
break
end
end
assert_nothing_raised("Failed to change user id") {
user[:uid] = newuid
}
trans = assert_events([:user_changed], comp, "user")
assert_equal(newuid, user.provider.uid, "UID was not changed")
assert_rollback_events(trans, [:user_changed], "user")
assert_equal(old, user.provider.uid, "UID was not reverted")
end
def attrtest_groups(user)
Etc.setgrent
max = 0
while group = Etc.getgrent
if group.gid > max and group.gid < 5000
max = group.gid
end
end
groups = []
main = []
extra = []
5.times do |i|
i += 1
name = "pptstgr%s" % i
groups << name
if i < 3
main << name
else
extra << name
end
end
assert(user[:membership] == :minimum, "Membership did not default correctly")
assert_nothing_raised {
user.retrieve
}
# Now add some of them to our user
assert_nothing_raised {
user[:groups] = extra
}
assert_nothing_raised {
user.retrieve
}
assert_instance_of(String, user.state(:groups).should)
# Some tests to verify that groups work correctly startig from nothing
# Remove our user
user[:ensure] = :absent
assert_apply(user)
assert_nothing_raised do
user.retrieve
end
# And add it again
user[:ensure] = :present
assert_apply(user)
# Make sure that the groups are a string, not an array
assert(user.provider.groups.is_a?(String),
"Incorrectly passed an array to groups")
user.retrieve
assert(user.state(:groups).is, "Did not retrieve group list")
list = user.state(:groups).is
assert_equal(extra.sort, list.sort, "Group list is not equal")
# Now set to our main list of groups
assert_nothing_raised {
user[:groups] = main
}
assert_equal((main + extra).sort, user.state(:groups).should.split(",").sort)
assert_nothing_raised {
user.retrieve
}
assert(!user.insync?, "User is incorrectly in sync")
assert_apply(user)
assert_nothing_raised {
user.retrieve
}
# We're not managing inclusively, so it should keep the old group
# memberships and add the new ones
list = user.state(:groups).is
assert_equal((main + extra).sort, list.sort, "Group list is not equal")
assert_nothing_raised {
user[:membership] = :inclusive
}
assert_nothing_raised {
user.retrieve
}
assert(!user.insync?, "User is incorrectly in sync")
assert_events([:user_changed], user)
assert_nothing_raised {
user.retrieve
}
list = user.state(:groups).is
assert_equal(main.sort, list.sort, "Group list is not equal")
# Set the values a bit differently.
user.state(:groups).should = list.sort { |a,b| b <=> a }
user.state(:groups).is = list.sort
assert(user.state(:groups).insync?, "Groups state did not sort groups")
user.delete(:groups)
end
def test_autorequire
file = tempfile()
comp = nil
user = nil
group =nil
home = nil
ogroup = nil
assert_nothing_raised {
user = Puppet.type(:user).create(
:name => "pptestu",
:home => file,
:gid => "pptestg",
:groups => "yayness"
)
home = Puppet.type(:file).create(
:path => file,
:owner => "pptestu",
:ensure => "directory"
)
group = Puppet.type(:group).create(
:name => "pptestg"
)
ogroup = Puppet.type(:group).create(
:name => "yayness"
)
comp = newcomp(user, group, home, ogroup)
}
- comp.finalize
- comp.retrieve
-
- assert(user.requires?(group), "User did not require group")
- assert(home.requires?(user), "Homedir did not require user")
- assert(user.requires?(ogroup), "User did not require other groups")
+
+ rels = nil
+ assert_nothing_raised() { rels = user.autorequire }
+
+ assert(rels.detect { |r| r.source == group }, "User did not require group")
+ assert(rels.detect { |r| r.source == ogroup }, "User did not require other groups")
+ assert_nothing_raised() { rels = home.autorequire }
+ assert(rels.detect { |r| r.source == user }, "Homedir did not require user")
end
def test_simpleuser
name = "pptest"
user = mkuser(name)
comp = newcomp("usercomp", user)
trans = assert_events([:user_created], comp, "user")
assert_equal(user.should(:comment), user.provider.comment,
"Comment was not set correctly")
assert_rollback_events(trans, [:user_removed], "user")
assert(! user.provider.exists?, "User did not get deleted")
end
def test_allusermodelstates
user = nil
name = "pptest"
user = mkuser(name)
assert(! user.provider.exists?, "User %s is present" % name)
comp = newcomp("usercomp", user)
trans = assert_events([:user_created], comp, "user")
user.retrieve
assert_equal("Puppet Testing User", user.provider.comment,
"Comment was not set")
tests = Puppet.type(:user).validstates
tests.each { |test|
if self.respond_to?("attrtest_%s" % test)
self.send("attrtest_%s" % test, user)
else
Puppet.err "Not testing attr %s of user" % test
end
}
user[:ensure] = :absent
assert_apply(user)
end
end
# $Id$