summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--lib/puppet/metatype/providers.rb68
-rw-r--r--lib/puppet/provider.rb6
-rwxr-xr-xlib/puppet/provider/mount/parsed.rb164
-rwxr-xr-xlib/puppet/provider/parsedfile.rb154
-rw-r--r--lib/puppet/type.rb8
-rwxr-xr-xlib/puppet/type/mount.rb38
-rw-r--r--lib/puppet/type/state.rb32
-rw-r--r--lib/puppet/util/fileparsing.rb49
-rw-r--r--test/lib/puppettest/fileparsing.rb30
-rwxr-xr-xtest/providers/parsedfile.rb201
-rwxr-xr-xtest/providers/parsedmount.rb120
-rwxr-xr-xtest/types/mount.rb90
-rwxr-xr-xtest/util/fileparsing.rb60
13 files changed, 698 insertions, 322 deletions
diff --git a/lib/puppet/metatype/providers.rb b/lib/puppet/metatype/providers.rb
index b1ce820be..0165da83e 100644
--- a/lib/puppet/metatype/providers.rb
+++ b/lib/puppet/metatype/providers.rb
@@ -41,6 +41,64 @@ class Puppet::Type
return @defaultprovider
end
+ # Convert a hash, as provided by, um, a provider, into an instance of self.
+ def self.hash2obj(hash)
+ obj = nil
+
+ namevar = self.namevar
+ unless hash.include?(namevar) and hash[namevar]
+ raise Puppet::DevError, "Hash was not passed with namevar"
+ end
+
+ # if the obj already exists with that name...
+ if obj = self[hash[namevar]]
+ # We're assuming here that objects with the same name
+ # are the same object, which *should* be the case, assuming
+ # we've set up our naming stuff correctly everywhere.
+
+ # Mark found objects as present
+ obj.is = [:ensure, :present]
+ obj.state(:ensure).found = :present
+ hash.each { |param, value|
+ if state = obj.state(param)
+ state.is = value
+ elsif val = obj[param]
+ obj[param] = val
+ else
+ # There is a value on disk, but it should go away
+ obj.is = [param, value]
+ obj[param] = :absent
+ end
+ }
+ else
+ # create a new obj, since no existing one seems to
+ # match
+ obj = self.create(namevar => hash[namevar])
+
+ # We can't just pass the hash in at object creation time,
+ # because it sets the should value, not the is value.
+ hash.delete(namevar)
+ hash.each { |param, value|
+ obj.is = [param, value]
+ }
+ end
+
+ return obj
+ end
+
+ # Create a list method that just calls our providers.
+ def self.mkprovider_list
+ unless respond_to?(:list)
+ meta_def(:list) do
+ suitableprovider.find_all { |p| p.respond_to?(:list) }.collect { |prov|
+ prov.list.each { |h| h[:provider] = prov.name }
+ }.flatten.collect do |hash|
+ hash2obj(hash)
+ end
+ end
+ end
+ end
+
# Retrieve a provider by name.
def self.provider(name)
name = Puppet::Util.symbolize(name)
@@ -67,9 +125,9 @@ class Puppet::Type
# directly on the type that it's implementing.
def self.provide(name, options = {}, &block)
name = Puppet::Util.symbolize(name)
- model = self
parent = if pname = options[:parent]
+ options.delete(:parent)
if pname.is_a? Class
pname
else
@@ -85,6 +143,8 @@ class Puppet::Type
Puppet::Type::Provider
end
+ options[:model] ||= self
+
self.providify
provider = genclass(name,
@@ -92,9 +152,7 @@ class Puppet::Type
:hash => @providers,
:prefix => "Provider",
:block => block,
- :attributes => {
- :model => model
- }
+ :attributes => options
)
return provider
@@ -105,6 +163,8 @@ class Puppet::Type
def self.providify
return if @paramhash.has_key? :provider
model = self
+
+ mkprovider_list()
newparam(:provider) do
desc "The specific backend for #{self.name.to_s} to use. You will
seldom need to specify this -- Puppet will usually discover the
diff --git a/lib/puppet/provider.rb b/lib/puppet/provider.rb
index 36c685990..a5890f131 100644
--- a/lib/puppet/provider.rb
+++ b/lib/puppet/provider.rb
@@ -51,7 +51,11 @@ class Puppet::Provider
#unless method_defined? name
unless metaclass.method_defined? name
meta_def(name) do |args|
- cmd = command(name) + " " + args
+ if args
+ cmd = command(name) + " " + args
+ else
+ cmd = command(name)
+ end
# This might throw an ExecutionFailure, but the system above
# will catch it, if so.
return execute(cmd)
diff --git a/lib/puppet/provider/mount/parsed.rb b/lib/puppet/provider/mount/parsed.rb
index 086cd5cd9..af67ca6ff 100755
--- a/lib/puppet/provider/mount/parsed.rb
+++ b/lib/puppet/provider/mount/parsed.rb
@@ -1,150 +1,43 @@
require 'puppet/provider/parsedfile'
-Puppet::Type.type(:mount).provide :parsed, :parent => Puppet::Provider::ParsedFile do
- @filetype = Puppet::FileType.filetype(:flat)
-
- commands :mountcmd => "mount", :umount => "umount", :df => "df"
-
- def self.init
- @platform = Facter["operatingsystem"].value
- case @platform
- when "Solaris":
- @path = "/etc/vfstab"
- @fields = [:device, :blockdevice, :path, :fstype, :pass, :atboot,
- :options]
- @fielddefaults = [ nil ] * @fields.size
- when "Darwin":
- @filetype = Puppet::FileType.filetype(:netinfo)
- @filetype.format = "fstab"
- @path = "mounts"
- @fields = [:device, :path, :fstype, :options, :dump, :pass]
- @fielddefaults = [ nil ] * @fields.size
-
- # How to map the dumped table to what we want
- @fieldnames = {
- "name" => :device,
- "dir" => :path,
- "dump_freq" => :dump,
- "passno" => :pass,
- "vfstype" => :fstype,
- "opts" => :options
- }
- else
- @path = "/etc/fstab"
- @fields = [:device, :path, :fstype, :options, :dump, :pass]
- @fielddefaults = [ nil ] * 4 + [ "0", "2" ]
- end
-
- # Allow Darwin to override the default filetype
- unless defined? @filetype
- @filetype = Puppet::FileType.filetype(:flat)
- end
- end
-
- init
-
- # XXX This needs to change to being a netinfo provider
- unless @platform == "Darwin"
- confine :exists => @path
- end
-
- def self.clear
- init
- super
- end
-
- # Parse a mount tab.
- #
- # This method also stores existing comments, and it stores all
- # mounts in order, mostly so that comments are retained in the
- # order they were written and in proximity to the same fses.
- def self.parse(text)
- # provide a single exception for darwin & netinfo
- if @filetype == Puppet::FileType.filetype(:netinfo)
- return parseninfo(text)
- end
- count = 0
-
- instances = []
- text.chomp.split("\n").each { |line|
- hash = {}
- case line
- when /^#/, /^\s*$/:
- # add comments and blank lines to the list as they are
- instances << line
- comment(line)
- else
- values = line.split(/\s+/)
- if @fields.length < values.length
- raise Puppet::Error, "Could not parse line %s" % line
- end
+fstab = nil
+case Facter.value(:operatingsystem)
+when "Solaris": fstab = "/etc/vfstab"
+else
+ fstab = "/etc/fstab"
+end
- values = @fielddefaults.zip(values).collect { |d, v| v || d }
- unless @fields.length == values.length
- raise Puppet::Error, "Could not parse line %s" % line
- end
-
- @fields.zip(values).each do |field, value|
- hash[field] = value
- end
+Puppet::Type.type(:mount).provide(:parsed,
+ :parent => Puppet::Provider::ParsedFile,
+ :default_target => fstab,
+ :filetype => :flat
+) do
- instances << hash
- count += 1
- end
- }
+ commands :mountcmd => "mount", :umount => "umount", :df => "df"
- return instances
+ @platform = Facter["operatingsystem"].value
+ case @platform
+ when "Solaris":
+ @fields = [:device, :blockdevice, :name, :fstype, :pass, :atboot,
+ :options]
+ else
+ @fields = [:device, :name, :fstype, :options, :dump, :pass]
+ @fielddefaults = [ nil ] * 4 + [ "0", "2" ]
end
- # Parse a netinfo table.
- def self.parseninfo(text)
- array = @fileobj.to_array(text)
+ text_line :comment, :match => /^\s*#/
+ text_line :blank, :match => /^\s*$/
- instances = []
- array.each do |record|
- hash = {}
- @fieldnames.each do |name, field|
- if value = record[name]
- if field == :options
- hash[field] = value.join(",")
- else
- hash[field] = value[0]
- end
- else
- raise ArgumentError, "Field %s was not provided" % [name]
- end
- end
-
- instances << hash
- end
-
- return instances
- end
-
- # Convert the current object into an fstab-style string.
- def self.to_record(hash)
- self.fields.collect do |field|
- if value = hash[field]
- value
- else
- raise Puppet::Error,
- "Could not retrieve value for %s" % field
- end
- end.join("\t")
- end
+ record_line self.name, :fields => @fields, :separator => /\s+/, :joiner => "\t"
# This only works when the mount point is synced to the fstab.
def mount
- mountcmd @model[:path]
+ mountcmd @model[:name]
end
# This only works when the mount point is synced to the fstab.
def unmount
- output = %x{#{command(:umount)} #{@model[:path]}}
-
- unless $? == 0
- raise Puppet::Error, "Could not unmount %s" % @model[:path]
- end
+ umount @model[:name]
end
# Is the mount currently mounted?
@@ -156,13 +49,12 @@ Puppet::Type.type(:mount).provide :parsed, :parent => Puppet::Provider::ParsedFi
when "Solaris": df = "#{command(:df)} -k"
end
%x{#{df}}.split("\n").find do |line|
- p line
fs = line.split(/\s+/)[-1]
if platform == "Darwin"
- fs == "/private/var/automount" + @model[:path] or
- fs == @model[:path]
+ fs == "/private/var/automount" + @model[:name] or
+ fs == @model[:name]
else
- fs == @model[:path]
+ fs == @model[:name]
end
end
end
diff --git a/lib/puppet/provider/parsedfile.rb b/lib/puppet/provider/parsedfile.rb
index f9e98d96a..5539679d0 100755
--- a/lib/puppet/provider/parsedfile.rb
+++ b/lib/puppet/provider/parsedfile.rb
@@ -13,17 +13,34 @@ class Puppet::Provider::ParsedFile < Puppet::Provider
extend Puppet::Util::FileParsing
class << self
- attr_reader :filetype
attr_accessor :default_target
end
attr_accessor :state_hash
+ def self.clean(hash)
+ newhash = hash.dup
+ [:record_type, :on_disk].each do |p|
+ if newhash.include?(p)
+ newhash.delete(p)
+ end
+ end
+
+ return newhash
+ end
+
def self.clear
@target_objects.clear
@records.clear
end
+ def self.filetype
+ unless defined? @filetype
+ @filetype = Puppet::FileType.filetype(:flat)
+ end
+ return @filetype
+ end
+
def self.filetype=(type)
if type.is_a?(Class)
@filetype = type
@@ -34,25 +51,37 @@ class Puppet::Provider::ParsedFile < Puppet::Provider
end
end
- # Flush all of the targets for which there are modified records.
+ # Flush all of the targets for which there are modified records. The only
+ # reason we pass a record here is so that we can add it to the stack if
+ # necessary -- it's passed from the instance calling 'flush'.
def self.flush(record)
- return unless defined?(@modified) and ! @modified.empty?
-
# Make sure this record is on the list to be flushed.
unless record[:on_disk]
record[:on_disk] = true
@records << record
+
+ # If we've just added the record, then make sure our
+ # target will get flushed.
+ modified(record[:target] || default_target)
end
+ return unless defined?(@modified) and ! @modified.empty?
+
+ flushed = []
@modified.sort { |a,b| a.to_s <=> b.to_s }.uniq.each do |target|
Puppet.debug "Flushing %s provider target %s" % [@model.name, target]
flush_target(target)
+ flushed << target
end
+
+ @modified.reject! { |t| flushed.include?(t) }
end
# Flush all of the records relating to a specific target.
def self.flush_target(target)
- target_object(target).write(to_file(target_records(target)))
+ target_object(target).write(to_file(target_records(target).reject { |r|
+ r[:ensure] == :absent
+ }))
end
# Return the header placed at the top of each generated file, warning
@@ -66,20 +95,43 @@ class Puppet::Provider::ParsedFile < Puppet::Provider
# Add another type var.
def self.initvars
@records = []
+ @target_objects = {}
# Default to flat files
@filetype = Puppet::FileType.filetype(:flat)
super
end
+ # Return a list of all of the records we can find.
+ def self.list
+ prefetch()
+ @records.find_all { |r| r[:record_type] == self.name }.collect { |r|
+ clean(r)
+ }
+ end
+
+ def self.list_by_name
+ list.collect { |r| r[:name] }
+ end
+
# Create attribute methods for each of the model's non-metaparam attributes.
def self.model=(model)
[model.validstates, model.parameters].flatten.each do |attr|
- define_method(attr) do @state_hash[attr] end
+ attr = symbolize(attr)
+ define_method(attr) do
+ # If it's not a valid field for this record type (which can happen
+ # when different platforms support different fields), then just
+ # return the should value, so the model shuts up.
+ if @state_hash[attr] or self.class.valid_attr?(self.class.name, attr)
+ @state_hash[attr] || :absent
+ else
+ @model.should(attr)
+ end
+ end
define_method(attr.to_s + "=") do |val|
# Mark that this target was modified.
- modeltarget = @model[:target]
+ modeltarget = @model[:target] || self.class.default_target
# If they're the same, then just mark that one as modified
if @state_hash[:target] and @state_hash[:target] == modeltarget
@@ -94,7 +146,7 @@ class Puppet::Provider::ParsedFile < Puppet::Provider
end
@state_hash[:target] = modeltarget
end
- @state_hash[attr] = val.to_s
+ @state_hash[attr] = val
end
end
@model = model
@@ -104,7 +156,7 @@ class Puppet::Provider::ParsedFile < Puppet::Provider
# used within the attr= methods.
def self.modified(target)
@modified ||= []
- @modified << target
+ @modified << target unless @modified.include?(target)
end
# Retrieve all of the data from disk. There are three ways to know
@@ -127,9 +179,10 @@ class Puppet::Provider::ParsedFile < Puppet::Provider
@records += retrieve(target).each do |r|
r[:on_disk] = true
r[:target] = target
+ r[:ensure] = :present
end
- # Retrieve the current state of this target.
+ # Set current state on any existing resource instances.
target_records(target).find_all { |i| i.is_a?(Hash) }.each do |record|
# Find any model instances whose names match our instances.
if instance = self.model[record[:name]]
@@ -140,11 +193,14 @@ class Puppet::Provider::ParsedFile < Puppet::Provider
instance.provider.state_hash = record
end
end
- # Any unmatched records are assumed to be unmanaged, so
- # we just leave them alone.
end
end
+ # Is there an existing record with this name?
+ def self.record?(name)
+ @records.find { |r| r[:name] == name }
+ end
+
# Retrieve the text for the file. Returns nil in the unlikely
# event that it doesn't exist.
def self.retrieve(path)
@@ -169,7 +225,6 @@ class Puppet::Provider::ParsedFile < Puppet::Provider
# Initialize the object if necessary.
def self.target_object(target)
- @target_objects ||= {}
@target_objects[target] ||= @filetype.new(target)
@target_objects[target]
@@ -198,55 +253,56 @@ class Puppet::Provider::ParsedFile < Puppet::Provider
targets << model[:target]
end
- targets.uniq
+ targets.uniq.reject { |t| t.nil? }
end
- # Write our data to disk.
- def flush
- # Make sure we've got a target and name set.
- @state_hash[:target] ||= @model[:target]
- @state_hash[:name] ||= @model[:name]
-
- self.class.flush(@state_hash)
+ def create
+ @model.class.validstates.each do |state|
+ if value = @model.should(state)
+ @state_hash[state] = value
+ end
+ end
+ self.class.modified(@state_hash[:target] || self.class.default_target)
end
- def initialize(model)
- super
-
- # Initialize our data hash with the record type. The record type
- # in the subclass has to create a record matching its name.
- @state_hash = {:record_type => self.class.name}
+ def destroy
+ # We use the method here so it marks the target as modified.
+ self.ensure = :absent
end
- # Have we been modified since the last flush?
- def modified?
- @state[:flush_needed]
+ def exists?
+ if @state_hash[:ensure] == :absent or @state_hash[:ensure].nil?
+ return false
+ else
+ return true
+ end
end
- def store(hash = nil)
- hash ||= self.model.to_hash
+ # Write our data to disk.
+ def flush
+ # Make sure we've got a target and name set.
- unless @instances
- self.hash
+ # If the target isn't set, then this is our first modification, so
+ # mark it for flushing.
+ unless @state_hash[:target]
+ @state_hash[:target] = @model[:target] || self.class.default_target
+ self.class.modified(@state_hash[:target])
end
+ @state_hash[:name] ||= @model.name
- if hash.empty?
- @me.clear
- else
- hash.each do |name, value|
- if @me[name] != hash[name]
- @me[name] = hash[name]
- end
- end
+ self.class.flush(@state_hash)
+ end
- @me.each do |name, value|
- unless hash.has_key? name
- @me.delete(name)
- end
- end
- end
+ def initialize(model)
+ super
- self.class.store(@instances)
+ # See if there's already a matching state_hash in the records list;
+ # else, use a default value.
+ # We provide a default for 'ensure' here, because the provider will
+ # override it if the thing exists, but it won't touch it if it doesn't
+ # exist.
+ @state_hash = self.class.record?(model[:name]) ||
+ {:record_type => self.class.name, :ensure => :absent}
end
end
diff --git a/lib/puppet/type.rb b/lib/puppet/type.rb
index 8df86ee81..1adeb1366 100644
--- a/lib/puppet/type.rb
+++ b/lib/puppet/type.rb
@@ -363,9 +363,11 @@ class Type < Puppet::Element
end
# Convert to a transportable object
- def to_trans
- # Collect all of the "is" values
- retrieve()
+ def to_trans(ret = true)
+ # Retrieve the object, if they tell use to.
+ if ret
+ retrieve()
+ end
trans = TransObject.new(self.title, self.class.name)
diff --git a/lib/puppet/type/mount.rb b/lib/puppet/type/mount.rb
index 518f21884..a6be65ae6 100755
--- a/lib/puppet/type/mount.rb
+++ b/lib/puppet/type/mount.rb
@@ -12,22 +12,30 @@ module Puppet
currently mounted, if it is ``mounted``, the filesystem
is entered into the mount table and mounted."
- newvalue(:present, :call => :after) do
+ newvalue(:present) do
if provider.mounted?
provider.unmount
return :mount_unmounted
else
+ provider.create
return :mount_created
end
end
+ aliasvalue :unmounted, :present
- newvalue(:absent, :event => :mount_deleted, :call => :after) do
+ newvalue(:absent, :event => :mount_deleted) do
if provider.mounted?
provider.unmount
end
+
+ provider.destroy
end
- newvalue(:mounted, :event => :mount_mounted, :call => :after) do
+ newvalue(:mounted, :event => :mount_mounted) do
+ # Create the mount point if it does not already exist.
+ if self.is == :absent or self.is.nil?
+ provider.create
+ end
# We have to flush any changes to disk.
@parent.flush
@@ -43,13 +51,10 @@ module Puppet
end
def retrieve
- fail "called retrieve"
- Puppet.warning @is.inspect
if provider.mounted?
@is = :mounted
else
- val = super()
- @is = val
+ @is = super()
end
end
@@ -107,12 +112,29 @@ module Puppet
support this."
end
- newparam(:path) do
+ newstate(:target) do
+ desc "The file in which to store the mount table. Only used by
+ those providers that write to disk (i.e., not NetInfo)."
+
+ defaultto { @parent.class.defaultprovider.default_target }
+ end
+
+ newparam(:name) do
desc "The mount path for the mount."
isnamevar
end
+ newparam(:path) do
+ desc "The deprecated name for the mount point. Please use ``name`` now."
+
+ def should=(value)
+ warning "'path' is deprecated for mounts. Please use 'name'."
+ @parent[:name] = value
+ super
+ end
+ end
+
@doc = "Manages mounted mounts, including putting mount
information into the mount table. The actual behavior depends
on the value of the 'ensure' parameter."
diff --git a/lib/puppet/type/state.rb b/lib/puppet/type/state.rb
index c639b671b..9faa6d8ec 100644
--- a/lib/puppet/type/state.rb
+++ b/lib/puppet/type/state.rb
@@ -272,7 +272,7 @@ class State < Puppet::Parameter
# Look for a matching value
@should.each { |val|
- if @is == val
+ if @is == val or @is == val.to_s
return true
end
}
@@ -457,11 +457,27 @@ class State < Puppet::Parameter
def self.defaultvalues
newvalue(:present) do
- @parent.create
+ if @parent.provider and @parent.provider.respond_to?(:create)
+ @parent.provider.create
+ else
+ @parent.create
+ end
end
newvalue(:absent) do
- @parent.destroy
+ if @parent.provider and @parent.provider.respond_to?(:destroy)
+ @parent.provider.destroy
+ else
+ @parent.destroy
+ end
+ end
+
+ defaultto do
+ if @parent.managed?
+ :present
+ else
+ nil
+ end
end
# This doc will probably get overridden
@@ -498,7 +514,15 @@ class State < Puppet::Parameter
# to get checked, which means that those other states do not have
# @is values set. This seems to be the source of quite a few bugs,
# although they're mostly logging bugs, not functional ones.
- if @parent.exists?
+ if prov = @parent.provider and prov.respond_to?(:exists?)
+ result = prov.exists?
+ elsif @parent.respond_to?(:exists?)
+ result = @parent.exists?
+ else
+ raise Puppet::DevError, "No ability to determine if %s exists" %
+ @parent.class.name
+ end
+ if result
@is = :present
else
@is = :absent
diff --git a/lib/puppet/util/fileparsing.rb b/lib/puppet/util/fileparsing.rb
index 705192b11..91a70c214 100644
--- a/lib/puppet/util/fileparsing.rb
+++ b/lib/puppet/util/fileparsing.rb
@@ -34,6 +34,15 @@ module Puppet::Util::FileParsing
@record_order.clear
end
+ def fields(type)
+ type = symbolize(type)
+ if @record_types.include?(type)
+ @record_types[type][:fields].dup
+ else
+ nil
+ end
+ end
+
# Try to match a specific text line.
def handle_text_line(line, hash)
if line =~ hash[:match]
@@ -46,10 +55,22 @@ module Puppet::Util::FileParsing
# Try to match a record.
def handle_record_line(line, hash)
if hash[:match]
+ raise "No provision yet for matching whole lines"
else
ret = {}
- hash[:fields].zip(line.split(hash[:separator])) do |param, value|
- ret[param] = value
+ sep = hash[:separator]
+
+ # String "helpfully" replaces ' ' with /\s+/ in splitting, so we
+ # have to work around it.
+ if sep == " "
+ sep = / /
+ end
+ hash[:fields].zip(line.split(sep)) do |param, value|
+ if value and value != ""
+ ret[param] = value
+ else
+ ret[param] = :absent
+ end
end
ret[:record_type] = hash[:name]
return ret
@@ -117,6 +138,8 @@ module Puppet::Util::FileParsing
r
end
+ options[:absent] ||= ""
+
options[:separator] ||= /\s+/
# Unless they specified a string-based joiner, just use a single
@@ -164,7 +187,14 @@ module Puppet::Util::FileParsing
else
joinchar = type[:joiner] || type[:separator]
- return type[:fields].collect { |field| details[field].to_s }.join(joinchar)
+ return type[:fields].collect { |field|
+ # If the field is marked absent, use the appropriate replacement
+ if details[field] == :absent
+ type[:absent]
+ else
+ details[field].to_s
+ end
+ }.join(joinchar)
end
end
@@ -177,6 +207,19 @@ module Puppet::Util::FileParsing
end
end
+ def valid_attr?(type, attr)
+ type = symbolize(type)
+ if @record_types[type] and @record_types[type][:fields].include?(symbolize(attr))
+ return true
+ else
+ if symbolize(attr) == :ensure
+ return true
+ else
+ false
+ end
+ end
+ end
+
private
# Define a new type of record.
def new_line_type(name, type, options)
diff --git a/test/lib/puppettest/fileparsing.rb b/test/lib/puppettest/fileparsing.rb
index 004a0c3b5..9f8124df5 100644
--- a/test/lib/puppettest/fileparsing.rb
+++ b/test/lib/puppettest/fileparsing.rb
@@ -1,28 +1,26 @@
module PuppetTest::FileParsing
# Run an isomorphism test on our parsing process.
def fakedataparse(file)
- @provider.path = file
- instances = nil
+ oldtarget = @provider.default_target
+ cleanup do
+ @provider.default_target = oldtarget
+ end
+ @provider.default_target = file
+
assert_nothing_raised {
- instances = @provider.retrieve
+ @provider.prefetch
}
- text = @provider.fileobj.read
+ text = @provider.to_file(@provider.target_records(file))
yield if block_given?
- dest = tempfile()
- @provider.path = dest
-
- # Now write it back out
- assert_nothing_raised {
- @provider.store(instances)
- }
-
- newtext = @provider.fileobj.read
-
- # Don't worry about difference in whitespace
- assert_equal(text.gsub(/\s+/, ' '), newtext.gsub(/\s+/, ' '))
+ oldlines = File.readlines(file)
+ newlines = text.chomp.split "\n"
+ oldlines.zip(newlines).each do |old, new|
+ assert_equal(old.chomp.gsub(/\s+/, ''), new.gsub(/\s+/, ''),
+ "Lines are not equal")
+ end
end
end
diff --git a/test/providers/parsedfile.rb b/test/providers/parsedfile.rb
index f7e5a4d48..e4c68dd0c 100755
--- a/test/providers/parsedfile.rb
+++ b/test/providers/parsedfile.rb
@@ -13,13 +13,14 @@ class TestParsedFile < Test::Unit::TestCase
include PuppetTest::FileParsing
Puppet::Type.newtype(:parsedfiletype) do
+ ensurable
newstate(:one) do
- newvalue(:a) {}
- newvalue(:b) {}
+ newvalue(:a)
+ newvalue(:b)
end
newstate(:two) do
- newvalue(:c) {}
- newvalue(:d) {}
+ newvalue(:c)
+ newvalue(:d)
end
newparam(:name) do
@@ -32,7 +33,9 @@ class TestParsedFile < Test::Unit::TestCase
# A simple block to skip the complexity of a full transaction.
def apply(model)
- [:one, :two].each do |st|
+ [:one, :two, :ensure].each do |st|
+ Puppet.info "Setting %s: %s => %s" %
+ [model[:name], st, model.should(st)]
model.provider.send(st.to_s + "=", model.should(st))
end
end
@@ -46,7 +49,8 @@ class TestParsedFile < Test::Unit::TestCase
end
def mkprovider(name = :parsed)
- @provider = @type.provide(name, :parent => Puppet::Provider::ParsedFile) do
+ @provider = @type.provide(name, :parent => Puppet::Provider::ParsedFile,
+ :filetype => :ram) do
record_line name, :fields => %w{name one two}
end
end
@@ -90,7 +94,7 @@ class TestParsedFile < Test::Unit::TestCase
end
# The provider converts to strings
- assert_equal("yayness", file.name)
+ assert_equal(:yayness, file.name)
end
def test_filetype
@@ -121,8 +125,8 @@ class TestParsedFile < Test::Unit::TestCase
obj = prov.target_object(path)
end
- # The default filetype is 'flat'
- assert_instance_of(Puppet::FileType.filetype(:flat), obj)
+ # The default filetype is 'ram'
+ assert_instance_of(Puppet::FileType.filetype(:ram), obj)
newobj = nil
assert_nothing_raised do
@@ -208,6 +212,24 @@ class TestParsedFile < Test::Unit::TestCase
assert_equal("b", model.provider.one)
assert_equal("b", default.provider.one)
assert_equal("d", default.provider.two)
+
+ # Now list all of them and make sure we get everything back
+ hashes = nil
+ assert_nothing_raised do
+ hashes = prov.list
+ end
+
+ names = nil
+ assert_nothing_raised do
+ names = prov.list_by_name
+ end
+
+ %w{bill jill will}.each do |name|
+ assert(hashes.find { |r| r[:name] == name},
+ "Did not return %s in list" % name)
+ assert(names.include?(name),
+ "Did not return %s in list_by_name" % name)
+ end
end
# Make sure we can correctly prefetch on a target.
@@ -316,8 +338,8 @@ class TestParsedFile < Test::Unit::TestCase
assert_nothing_raised { one.flush }
# Make sure it changed our file
- assert_equal("a", one.provider.one)
- assert_equal("c", one.provider.two)
+ assert_equal(:a, one.provider.one)
+ assert_equal(:c, one.provider.two)
# And make sure it's right on disk
assert(prov.target_object(:yayness).read.include?("one a c"),
@@ -340,17 +362,19 @@ class TestParsedFile < Test::Unit::TestCase
assert_nothing_raised { apply(two) }
assert_nothing_raised { two.flush }
- assert_equal("b", two.provider.one)
+ assert_equal(:b, two.provider.one)
end
def test_creating_file
prov = mkprovider
+ prov.clear
- prov.filetype = :ram
prov.default_target = :basic
model = mkmodel "yay", :target => :basic, :one => "a", :two => "c"
+ assert_equal(:present, model.should(:ensure))
+
apply(model)
assert_nothing_raised do
@@ -385,16 +409,17 @@ class TestParsedFile < Test::Unit::TestCase
second = mkmodel "second", :target => :second
mover = mkmodel "mover", :target => :first
- # Apply them all
[first, second, mover].each do |m|
assert_nothing_raised("Could not apply %s" % m[:name]) do
apply(m)
end
end
- # Flush
- assert_nothing_raised do
- [first, second, mover].each do |m| m.flush end
+ # Flush.
+ [first, second, mover].each do |m|
+ assert_nothing_raised do
+ m.flush
+ end
end
check = proc do |target, name|
@@ -428,6 +453,148 @@ class TestParsedFile < Test::Unit::TestCase
assert(prov.target_object(:first) !~ /mover/,
"Mover was not removed from first file")
end
+
+ # Make sure that 'ensure' correctly calls 'sync' on all states.
+ def test_ensure
+ prov = mkprovider
+
+ prov.filetype = :ram
+ prov.default_target = :first
+
+ # Make two models, one that starts on disk and one that doesn't
+ ondisk = mkmodel "ondisk", :target => :first
+ notdisk = mkmodel "notdisk", :target => :first
+
+ prov.target_object(:first).write "ondisk a c\n"
+ prov.prefetch
+
+ assert_equal(:present, notdisk.should(:ensure),
+ "Did not get default ensure value")
+
+ # Try creating the object
+ assert_nothing_raised { notdisk.provider.create() }
+
+ # Now make sure all of the data is copied over correctly.
+ notdisk.class.validstates.each do |state|
+ assert_equal(notdisk.should(state), notdisk.provider.state_hash[state],
+ "%s was not copied over during creation" % state)
+ end
+
+ # Flush it to disk and make sure it got copied down
+ assert_nothing_raised do
+ notdisk.flush
+ end
+
+ assert(prov.target_object(:first).read =~ /^notdisk/,
+ "Did not write out object to disk")
+ assert(prov.target_object(:first).read =~ /^ondisk/,
+ "Lost object on disk")
+
+ # Make sure our on-disk model behaves appropriately.
+ assert_equal(:present, ondisk.provider.ensure)
+
+ # Now destroy the object
+ assert_nothing_raised { notdisk.provider.destroy() }
+
+ assert_nothing_raised { notdisk.flush }
+
+ # And make sure it's no longer present
+ assert(prov.target_object(:first).read !~ /^notdisk/,
+ "Did not remove thing from disk")
+ assert(prov.target_object(:first).read =~ /^ondisk/,
+ "Lost object on disk")
+ assert_equal(:present, ondisk.provider.ensure)
+ end
+
+ def test_absent_fields
+ prov = @type.provide(:record, :parent => Puppet::Provider::ParsedFile) do
+ record_line :record, :fields => %w{name one two},
+ :separator => "\s"
+ end
+ cleanup { @type.unprovide(:record) }
+
+ records = prov.parse("a d")
+
+ line = records.find { |r| r[:name] == "a" }
+ assert(line, "Could not find line")
+
+ assert_equal(:absent, line[:one], "field one was not set to absent")
+ end
+
+ # This test is because in x2puppet I was having problems where multiple
+ # retrievals somehow destroyed the 'is' values.
+ def test_value_retrieval
+ prov = mkprovider
+ prov.default_target = :yayness
+
+ prov.target_object(:yayness).write "bill a c\njill b d"
+
+ list = Puppet::Type.type(:parsedfiletype).list
+
+ bill = list.find { |r| r[:name] == "bill" }
+ jill = list.find { |r| r[:name] == "jill" }
+ assert(bill, "Could not find bill")
+ assert(jill, "Could not find jill")
+
+ prov = bill.provider
+
+ 4.times do |i|
+ assert(prov.one, "Did not get a value for 'one' on try %s" % (i + 1))
+ end
+
+ # First make sure we can retrieve values multiple times from the
+ # provider
+ assert(bill.is(:one), "Bill does not have a value for 'one'")
+ assert(bill.is(:one), "Bill does not have a value for 'one' on second try")
+ assert_nothing_raised do
+ bill.retrieve
+ end
+ assert(bill.is(:one), "bill's value for 'one' disappeared")
+ end
+
+ # Make sure that creating a new model finds existing records in memory
+ def test_initialize_finds_records
+ prov = mkprovider
+ prov.default_target = :yayness
+
+ prov.target_object(:yayness).write "bill a c\njill b d"
+
+ prov.prefetch
+
+ # Now make a model
+ bill = nil
+ assert_nothing_raised do
+ bill = Puppet::Type.type(:parsedfiletype).create :name => "bill"
+ end
+
+ assert_equal("a", bill.provider.one,
+ "Record was not found in memory")
+ end
+
+ # Make sure invalid fields always show up as insync
+ def test_invalid_fields
+ prov = @type.provide(:test, :parent => Puppet::Provider::ParsedFile,
+ :filetype => :ram, :default_target => :yayness) do
+ record_line :test, :fields => %w{name two}
+ end
+ cleanup do @type.unprovide(:test) end
+
+ bill = nil
+ assert_nothing_raised do
+ bill = Puppet::Type.type(:parsedfiletype).create :name => "bill",
+ :one => "a", :two => "c"
+ end
+
+ assert_apply(bill)
+
+ prov.prefetch
+ assert_nothing_raised do
+ bill.retrieve
+ end
+
+ assert(bill.insync?,
+ "An invalid field marked the record out of sync")
+ end
end
# $Id$
diff --git a/test/providers/parsedmount.rb b/test/providers/parsedmount.rb
index 0d146df34..868dde617 100755
--- a/test/providers/parsedmount.rb
+++ b/test/providers/parsedmount.rb
@@ -13,7 +13,8 @@ class TestParsedMounts < Test::Unit::TestCase
def setup
super
- @provider = Puppet.type(:mount).provider(:parsed)
+ @mount = Puppet.type(:mount)
+ @provider = @mount.provider(:parsed)
@oldfiletype = @provider.filetype
end
@@ -21,6 +22,7 @@ class TestParsedMounts < Test::Unit::TestCase
def teardown
Puppet::FileType.filetype(:ram).clear
@provider.filetype = @oldfiletype
+ @provider.clear
super
end
@@ -33,13 +35,13 @@ class TestParsedMounts < Test::Unit::TestCase
@pcount = 1
end
args = {
- :path => "/fspuppet%s" % @pcount,
+ :name => "/fspuppet%s" % @pcount,
:device => "/dev/dsk%s" % @pcount,
}
- @provider.fields.each do |field|
+ @provider.fields(:parsed).each do |field|
unless args.include? field
- args[field] = "fake%s" % @pcount
+ args[field] = "fake%s%s" % [field, @pcount]
end
end
@@ -50,15 +52,13 @@ class TestParsedMounts < Test::Unit::TestCase
hash = mkmountargs()
#hash[:provider] = @provider.name
- fakemodel = fakemodel(:mount, hash[:path])
+ fakemodel = fakemodel(:mount, hash[:name])
mount = @provider.new(fakemodel)
- #mount = Puppet.type(:mount).create(hash)
-
- hash.each do |name, val|
- fakemodel[name] = val
- end
assert(mount, "Could not create provider mount")
+ hash[:record_type] = :parsed
+ hash[:ensure] = :present
+ mount.state_hash = hash
return mount
end
@@ -69,11 +69,22 @@ class TestParsedMounts < Test::Unit::TestCase
@provider.filetype = Puppet::FileType.filetype(:ram)
end
+ def test_default_target
+ should = case Facter.value(:operatingsystem)
+ when "Solaris": "/etc/vfstab"
+ else
+ "/etc/fstab"
+ end
+ assert_equal(should, @provider.default_target)
+ end
+
def test_simplemount
mkfaketype
- assert_nothing_raised {
- assert_equal([], @provider.retrieve)
- }
+ target = @provider.default_target
+
+ # Make sure we start with an empty file
+ assert_equal("", @provider.target_object(target).read,
+ "Got a non-empty starting file")
# Now create a provider
mount = nil
@@ -82,50 +93,49 @@ class TestParsedMounts < Test::Unit::TestCase
}
# Make sure we're still empty
- assert_nothing_raised {
- assert_equal([], @provider.retrieve)
- }
+ assert_equal("", @provider.target_object(target).read,
+ "Got a non-empty starting file")
- hash = mount.model.to_hash
-
- # Try storing it
+ # Try flushing it to disk
assert_nothing_raised do
- mount.store(hash)
+ mount.flush
end
- # Make sure we get the mount back
- assert_nothing_raised {
- assert_equal([hash], @provider.retrieve)
- }
-
- # Now remove the whole object
- assert_nothing_raised {
- mount.store({})
- assert_equal([], @provider.retrieve)
- }
+ # Make sure it's now in the file. The file format is validated in
+ # the isomorphic methods.
+ assert(@provider.target_object(target).read.include?("\t%s\t" %
+ mount.state_hash[:name]), "Mount was not written to disk")
end
unless Facter["operatingsystem"].value == "Darwin"
def test_mountsparse
- fakedataparse(fake_fstab) do
+ tab = fake_fstab
+ fakedataparse(tab) do
# Now just make we've got some mounts we know will be there
- hashes = @provider.retrieve.find_all { |i| i.is_a? Hash }
+ hashes = @provider.target_records(tab).find_all { |i| i.is_a? Hash }
assert(hashes.length > 0, "Did not create any hashes")
- root = hashes.find { |i| i[:path] == "/" }
+ root = hashes.find { |i| i[:name] == "/" }
assert(root, "Could not retrieve root mount")
end
end
def test_rootfs
fs = nil
- @provider.path = fake_fstab()
- fakemodel = fakemodel(:mount, "/")
- mount = @provider.new(fakemodel)
- mount.model[:path] = "/"
- assert(mount.hash, "Could not retrieve root fs")
+ type = @mount.create :name => "/"
+
+ provider = type.provider
+
+ assert(FileTest.exists?(@provider.default_target),
+ "FSTab %s does not exist" % @provider.default_target)
+ assert_nothing_raised do
+ @provider.prefetch
+ end
+
+ assert_equal(:present, provider.state_hash[:ensure],
+ "Could not find root fs")
assert_nothing_raised {
- assert(mount.mounted?, "Root is considered not mounted")
+ assert(provider.mounted?, "Root is considered not mounted")
}
end
end
@@ -133,44 +143,28 @@ class TestParsedMounts < Test::Unit::TestCase
if Puppet::SUIDManager.uid == 0
def test_mountfs
fs = nil
- case Facter["hostname"].value
+ case Facter.value(:hostname)
when "culain": fs = "/ubuntu"
when "atalanta": fs = "/mnt"
- when "figurehead": fs = "/cg4/net/depts"
else
$stderr.puts "No mount for mount testing; skipping"
return
end
- oldtext = @provider.fileobj.read
+ oldtext = @provider.target_object(@provider.default_target).read
ftype = @provider.filetype
- # Make sure the original gets reinstalled.
- if ftype == Puppet::FileType.filetype(:netinfo)
- cleanup do
- IO.popen("niload -r /mounts .", "w") do |file|
- file.puts oldtext
- end
- end
- else
- cleanup do
- @provider.fileobj.write(oldtext)
- end
- end
-
- fakemodel = fakemodel(:mount, "/")
- obj = @provider.new(fakemodel)
- obj.model[:path] = fs
+ mount = @mount.create :name => fs
+ obj = mount.provider
current = nil
-
assert_nothing_raised {
current = obj.mounted?
}
if current
- # Make sure the original gets reinstalled.
+ # Make sure the original gets remounted.
cleanup do
unless obj.mounted?
obj.mount
@@ -188,10 +182,13 @@ class TestParsedMounts < Test::Unit::TestCase
obj.unmount
}
assert(! obj.mounted?, "FS still mounted")
+ # Check the actual output of df
+ assert(! obj.df(nil).include?(fs), "%s is still listed in df" % fs)
assert_nothing_raised {
obj.mount
}
assert(obj.mounted?, "FS not mounted")
+ assert(obj.df(nil).include?(fs), "%s is not listed in df" % fs)
end
end
@@ -206,8 +203,7 @@ class TestParsedMounts < Test::Unit::TestCase
# Catchall for other fstabs
name = "linux.fstab"
end
- oldpath = @provider.path
- cleanup do @provider.path = oldpath end
+ oldpath = @provider.default_target
return fakefile(File::join("data/types/mount", name))
end
end
diff --git a/test/types/mount.rb b/test/types/mount.rb
index 880de4434..5a5a5da85 100755
--- a/test/types/mount.rb
+++ b/test/types/mount.rb
@@ -14,11 +14,20 @@ class TestMounts < Test::Unit::TestCase
attr_accessor :mounted
+ def self.default_target
+ :yayness
+ end
+
def create
@ensure = :present
+ @model.class.validstates.each do |state|
+ if value = @model.should(state)
+ self.send(state.to_s + "=", value)
+ end
+ end
end
- def delete
+ def destroy
@ensure = :absent
@mounted = false
end
@@ -50,12 +59,14 @@ class TestMounts < Test::Unit::TestCase
def setup
super
- @realprovider = Puppet::Type.type(:mount).defaultprovider
- Puppet::Type.type(:mount).defaultprovider = FakeMountProvider
+ @mount = Puppet::Type.type(:mount)
+ @realprovider = @mount.defaultprovider
+ @mount.defaultprovider = FakeMountProvider
end
def teardown
Puppet.type(:mount).clear
+ @realprovider.clear
Puppet::Type.type(:mount).defaultprovider = nil
super
end
@@ -73,7 +84,10 @@ class TestMounts < Test::Unit::TestCase
:device => "/dev/dsk%s" % @pcount,
}
- @realprovider.fields.each do |field|
+ [@mount.validstates, @mount.parameters].flatten.each do |field|
+ next if field == :provider
+ next if field == :target
+ next if field == :ensure
unless args.include? field
args[field] = "fake%s" % @pcount
end
@@ -87,24 +101,13 @@ class TestMounts < Test::Unit::TestCase
end
def test_simplemount
- mount = nil
- oldprv = Puppet.type(:mount).defaultprovider
- Puppet.type(:mount).defaultprovider = nil
- assert_nothing_raised {
- Puppet.type(:mount).defaultprovider.retrieve
-
- count = 0
- Puppet.type(:mount).each do |h|
- count += 1
- end
-
- assert_equal(0, count, "Found mounts in empty file somehow")
- }
- Puppet.type(:mount).defaultprovider = oldprv
-
mount = mkmount
assert_apply(mount)
+ mount.send(:states).each do |state|
+ assert_equal(state.should, mount.provider.send(state.name),
+ "%s was not set to %s" % [state.name, state.should])
+ end
assert_events([], mount)
assert_nothing_raised { mount.retrieve }
@@ -155,11 +158,60 @@ class TestMounts < Test::Unit::TestCase
def test_list
list = nil
+ assert(@mount.respond_to?(:list),
+ "No list method defined for mount")
+
assert_nothing_raised do
list = Puppet::Type.type(:mount).list
end
assert(list.length > 0, "Did not return any mounts")
+
+ root = list.find { |o| o[:name] == "/" }
+ assert(root, "Could not find root root filesystem in list results")
+
+ assert(root.is(:device), "Device was not set")
+ assert(root.state(:device).value, "Device was not returned by value method")
+
+ assert_nothing_raised do
+ root.retrieve
+ end
+
+ assert(root.is(:device), "Device was not set")
+ assert(root.state(:device).value, "Device was not returned by value method")
+ end
+
+ # Make sure we actually remove the object from the file and such.
+ def test_removal
+ # Reset the provider so that we're using the real thing
+ @mount.defaultprovider = nil
+
+ provider = @mount.defaultprovider
+ assert(provider, "Could not retrieve default provider")
+
+ file = provider.default_target
+ assert(FileTest.exists?(file),
+ "FSTab %s does not exist" % file)
+
+ # Now switch to ram, so we're just doing this there, not really on disk.
+ provider.filetype = :ram
+ #provider.target_object(file).write text
+
+ mount = mkmount
+
+ mount[:ensure] = :present
+
+ assert_events([:mount_created], mount)
+ assert_events([], mount)
+
+ mount[:ensure] = :absent
+ assert_events([:mount_deleted], mount)
+ assert_events([], mount)
+
+ # Now try listing and making sure the object is actually gone.
+ list = mount.provider.class.list
+ assert(! list.find { |r| r[:name] == mount[:name] },
+ "Mount was not actually removed")
end
end
diff --git a/test/util/fileparsing.rb b/test/util/fileparsing.rb
index f5cf0d255..cebc3051e 100755
--- a/test/util/fileparsing.rb
+++ b/test/util/fileparsing.rb
@@ -273,6 +273,51 @@ class TestUtilFileParsing < Test::Unit::TestCase
end
end
+ # Make sure fields that are marked absent get replaced with the appropriate
+ # string.
+ def test_absent_fields
+ parser = FParser.new
+
+ options = nil
+ assert_nothing_raised do
+ options = parser.record_line :record, :fields => %w{one two three}
+ end
+ assert_equal("", options[:absent], "Did not set a default absent string")
+
+ result = nil
+ assert_nothing_raised do
+ result = parser.to_line(:record_type => :record,
+ :one => "a", :two => :absent, :three => "b")
+ end
+
+ assert_equal("a b", result, "Absent was not correctly replaced")
+
+ # Now try using a different replacement character
+ options[:absent] = "*" # Because cron is a pain in my ass
+ assert_nothing_raised do
+ result = parser.to_line(:record_type => :record,
+ :one => "a", :two => :absent, :three => "b")
+ end
+
+ assert_equal("a * b", result, "Absent was not correctly replaced")
+
+ # Make sure we deal correctly with the string 'absent'
+ assert_nothing_raised do
+ result = parser.to_line(:record_type => :record,
+ :one => "a", :two => "b", :three => 'absent')
+ end
+
+ assert_equal("a b absent", result, "Replaced string 'absent'")
+
+ # And, of course, make sure we can swap things around.
+ assert_nothing_raised do
+ result = parser.to_line(:record_type => :record,
+ :one => "a", :two => "b", :three => :absent)
+ end
+
+ assert_equal("a b *", result, "Absent was not correctly replaced")
+ end
+
# Make sure we can specify a different join character than split character
def test_split_join_record_line
parser = FParser.new
@@ -331,6 +376,21 @@ billy three four"
end
end
+ def test_valid_attrs
+ parser = FParser.new
+
+ parser.record_line :record, :fields => %w{one two three}
+
+ assert(parser.valid_attr?(:record, :one),
+ "one was considered invalid")
+
+ assert(parser.valid_attr?(:record, :ensure),
+ "ensure was considered invalid")
+
+ assert(! parser.valid_attr?(:record, :four),
+ "four was considered valid")
+ end
+
# Make sure we correctly handle optional fields. We'll skip this
# functionality until we really know we need it.
def disabled_test_optional_fields