class Puppet::Provider::ParsedFile

This provider can be used as the parent class for a provider that parses and generates files. Its content must be loaded via the 'prefetch' method, and the file will be written when 'flush' is called on the provider instance. At this point, the file is written once for every provider instance.

Once the provider prefetches the data, it's the resource's job to copy that data over to the @is variables.

NOTE: The prefetch method swallows FileReadErrors by treating the corresponding target as an empty file. If you would like to turn this behavior off, then set the raise_prefetch_errors class variable to true. Doing so will error all resources associated with the failed target.

Attributes

default_target[RW]
raise_prefetch_errors[RW]
target[RW]
property_hash[RW]

Public Class Methods

backup_target(target) click to toggle source

Make sure our file is backed up, but only back it up once per transaction. We cheat and rely on the fact that @records is created on each prefetch.

   # File lib/puppet/provider/parsedfile.rb
89 def self.backup_target(target)
90   return nil unless target_object(target).respond_to?(:backup)
91 
92   @backup_stats ||= {}
93   return nil if @backup_stats[target] == @records.object_id
94 
95   target_object(target).backup
96   @backup_stats[target] = @records.object_id
97 end
clean(hash) click to toggle source
   # File lib/puppet/provider/parsedfile.rb
28 def self.clean(hash)
29   newhash = hash.dup
30   [:record_type, :on_disk].each do |p|
31     newhash.delete(p) if newhash.include?(p)
32   end
33 
34   newhash
35 end
clear() click to toggle source
   # File lib/puppet/provider/parsedfile.rb
37 def self.clear
38   @target_objects.clear
39   @records.clear
40 end
default_mode() click to toggle source

The mode for generated files if they are newly created. No mode will be set on existing files.

@abstract Providers inheriting parsedfile can override this method

to provide a mode. The value should be suitable for File.chmod
    # File lib/puppet/provider/parsedfile.rb
353 def self.default_mode
354   nil
355 end
drop_native_header() click to toggle source

How to handle third party headers. @api private @abstract Providers based on ParsedFile that make use of the support for

third party headers may override this method to return +true+.
When this is done, headers that are matched by the native_header_regex
are not written back to disk.

@see native_header_regex

    # File lib/puppet/provider/parsedfile.rb
149 def self.drop_native_header
150   false
151 end
filetype() click to toggle source
   # File lib/puppet/provider/parsedfile.rb
42 def self.filetype
43   @filetype ||= Puppet::Util::FileType.filetype(:flat)
44 end
filetype=(type) click to toggle source
   # File lib/puppet/provider/parsedfile.rb
46 def self.filetype=(type)
47   if type.is_a?(Class)
48     @filetype = type
49   else
50     klass = Puppet::Util::FileType.filetype(type)
51     if klass
52       @filetype = klass
53     else
54       raise ArgumentError, _("Invalid filetype %{type}") % { type: type }
55     end
56   end
57 end
flush(record) click to toggle source

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'.

   # File lib/puppet/provider/parsedfile.rb
62 def self.flush(record)
63   # Make sure this record is on the list to be flushed.
64   unless record[:on_disk]
65     record[:on_disk] = true
66     @records << record
67 
68     # If we've just added the record, then make sure our
69     # target will get flushed.
70     modified(record[:target] || default_target)
71   end
72 
73   return unless defined?(@modified) and ! @modified.empty?
74 
75   flushed = []
76   begin
77     @modified.sort_by(&:to_s).uniq.each do |target|
78       Puppet.debug "Flushing #{@resource_type.name} provider target #{target}"
79       flushed << target
80       flush_target(target)
81     end
82   ensure
83     @modified.reject! { |t| flushed.include?(t) }
84   end
85 end
flush_target(target) click to toggle source

Flush all of the records relating to a specific target.

    # File lib/puppet/provider/parsedfile.rb
100 def self.flush_target(target)
101   if @raise_prefetch_errors && @failed_prefetch_targets.key?(target)
102     raise Puppet::Error, _("Failed to read %{target}'s records when prefetching them. Reason: %{detail}") % { target: target, detail: @failed_prefetch_targets[target] }
103   end
104 
105   backup_target(target)
106 
107   records = target_records(target).reject { |r|
108     r[:ensure] == :absent
109   }
110 
111   target_object(target).write(to_file(records))
112 end
header() click to toggle source

Return the header placed at the top of each generated file, warning users that modifying this file manually is probably a bad idea.

    # File lib/puppet/provider/parsedfile.rb
116   def self.header
117 %{# HEADER: This file was autogenerated at #{Time.now}
118 # HEADER: by puppet.  While it can still be managed manually, it
119 # HEADER: is definitely not recommended.\n}
120   end
initvars() click to toggle source

Add another type var.

Calls superclass method Puppet::Provider::initvars
    # File lib/puppet/provider/parsedfile.rb
154 def self.initvars
155   @records = []
156   @target_objects = {}
157 
158   # Hash of <target> => <failure reason>.
159   @failed_prefetch_targets = {}
160   @raise_prefetch_errors = false
161 
162   @target = nil
163 
164   # Default to flat files
165   @filetype ||= Puppet::Util::FileType.filetype(:flat)
166   super
167 end
instances() click to toggle source

Return a list of all of the records we can find.

    # File lib/puppet/provider/parsedfile.rb
170 def self.instances
171   targets.collect do |target|
172     prefetch_target(target)
173   end.flatten.reject { |r| skip_record?(r) }.collect do |record|
174     new(record)
175   end
176 end
match_providers_with_resources(resources) click to toggle source

Match a list of catalog resources with provider instances

@api private

@param [Array<Puppet::Resource>] resources A list of resources using this class as a provider

    # File lib/puppet/provider/parsedfile.rb
236 def self.match_providers_with_resources(resources)
237   return unless resources
238   matchers = resources.dup
239   @records.each do |record|
240     # Skip things like comments and blank lines
241     next if skip_record?(record)
242 
243     if (resource = resource_for_record(record, resources))
244       resource.provider = new(record)
245     elsif respond_to?(:match)
246       resource = match(record, matchers)
247       if resource
248         matchers.delete(resource.title)
249         record[:name] = resource[:name]
250         resource.provider = new(record)
251       end
252     end
253   end
254 end
mk_resource_methods() click to toggle source

Override the default method with a lot more functionality.

    # File lib/puppet/provider/parsedfile.rb
179 def self.mk_resource_methods
180   [resource_type.validproperties, resource_type.parameters].flatten.each do |attr|
181     attr = attr.intern
182     define_method(attr) do
183       # If it's not a valid field for this record type (which can happen
184       # when different platforms support different fields), then just
185       # return the should value, so the resource shuts up.
186       if @property_hash[attr] or self.class.valid_attr?(self.class.name, attr)
187         @property_hash[attr] || :absent
188       else
189         if defined?(@resource)
190           @resource.should(attr)
191         else
192           nil
193         end
194       end
195     end
196 
197     define_method(attr.to_s + "=") do |val|
198       mark_target_modified
199       @property_hash[attr] = val
200     end
201   end
202 end
modified(target) click to toggle source

Mark a target as modified so we know to flush it. This only gets used within the attr= methods.

    # File lib/puppet/provider/parsedfile.rb
212 def self.modified(target)
213   @modified ||= []
214   @modified << target unless @modified.include?(target)
215 end
native_header_regex() click to toggle source

An optional regular expression matched by third party headers.

For example, this can be used to filter the vixie cron headers as erroneously exported by older cron versions.

@api private @abstract Providers based on ParsedFile may implement this to make it

possible to identify a header maintained by a third party tool.
The provider can then allow that header to remain near the top of the
written file, or remove it after composing the file content.
If implemented, the function must return a Regexp object.
The expression must be tailored to match exactly one third party header.

@see drop_native_header @note When specifying regular expressions in multiline mode, avoid

greedy repetitions such as '.*' (use .*? instead). Otherwise, the
provider may drop file content between sparse headers.
    # File lib/puppet/provider/parsedfile.rb
138 def self.native_header_regex
139   nil
140 end
new(record) click to toggle source
Calls superclass method Puppet::Provider::new
    # File lib/puppet/provider/parsedfile.rb
461 def initialize(record)
462   super
463 
464   # The 'record' could be a resource or a record, depending on how the provider
465   # is initialized.  If we got an empty property hash (probably because the resource
466   # is just being initialized), then we want to set up some defaults.
467   @property_hash = self.class.record?(resource[:name]) || {:record_type => self.class.name, :ensure => :absent} if @property_hash.empty?
468 end
prefetch(resources = nil) click to toggle source

Retrieve all of the data from disk. There are three ways to know which files to retrieve: We might have a list of file objects already set up, there might be instances of our associated resource and they will have a path parameter set, and we will have a default path set. We need to turn those three locations into a list of files, prefetch each one, and make sure they're associated with each appropriate resource instance.

    # File lib/puppet/provider/parsedfile.rb
224 def self.prefetch(resources = nil)
225   # Reset the record list.
226   @records = prefetch_all_targets(resources)
227 
228   match_providers_with_resources(resources)
229 end
prefetch_all_targets(resources) click to toggle source
    # File lib/puppet/provider/parsedfile.rb
271 def self.prefetch_all_targets(resources)
272   records = []
273   targets(resources).each do |target|
274     records += prefetch_target(target)
275   end
276   records
277 end
prefetch_target(target) click to toggle source

Prefetch an individual target.

    # File lib/puppet/provider/parsedfile.rb
280 def self.prefetch_target(target)
281   begin
282     target_records = retrieve(target)
283     unless target_records
284       raise Puppet::DevError, _("Prefetching %{target} for provider %{name} returned nil") % { target: target, name: self.name }
285     end
286   rescue Puppet::Util::FileType::FileReadError => detail
287     if @raise_prefetch_errors
288       # We will raise an error later in flush_target. This way,
289       # only the resources linked to our target will fail
290       # evaluation.
291       @failed_prefetch_targets[target] = detail.to_s
292     else
293       puts detail.backtrace if Puppet[:trace]
294       Puppet.err _("Could not prefetch %{resource} provider '%{name}' target '%{target}': %{detail}. Treating as empty") % { resource: self.resource_type.name, name: self.name, target: target, detail: detail }
295     end
296 
297     target_records = []
298   end
299 
300   target_records.each do |r|
301     r[:on_disk] = true
302     r[:target] = target
303     r[:ensure] = :present
304   end
305 
306   target_records = prefetch_hook(target_records) if respond_to?(:prefetch_hook)
307 
308   raise Puppet::DevError, _("Prefetching %{target} for provider %{name} returned nil") % { target: target, name: self.name } unless target_records
309 
310   target_records
311 end
record?(name) click to toggle source

Is there an existing record with this name?

    # File lib/puppet/provider/parsedfile.rb
314 def self.record?(name)
315   return nil unless @records
316   @records.find { |r| r[:name] == name }
317 end
resource_for_record(record, resources) click to toggle source

Look up a resource based on a parsed file record

@api private

@param [Hash<Symbol, Object>] record @param [Array<Puppet::Resource>] resources

@return [Puppet::Resource, nil] The resource if found, else nil

    # File lib/puppet/provider/parsedfile.rb
264 def self.resource_for_record(record, resources)
265   name = record[:name]
266   if name
267     resources[name]
268   end
269 end
resource_type=(resource) click to toggle source

Always make the resource methods.

Calls superclass method
    # File lib/puppet/provider/parsedfile.rb
205 def self.resource_type=(resource)
206   super
207   mk_resource_methods
208 end
retrieve(path) click to toggle source

Retrieve the text for the file. Returns nil in the unlikely event that it doesn't exist.

    # File lib/puppet/provider/parsedfile.rb
321 def self.retrieve(path)
322   # XXX We need to be doing something special here in case of failure.
323   text = target_object(path).read
324   if text.nil? or text == ""
325     # there is no file
326     return []
327   else
328     # Set the target, for logging.
329     old = @target
330     begin
331       @target = path
332       return self.parse(text)
333     rescue Puppet::Error => detail
334       detail.file = @target if detail.respond_to?(:file=)
335       raise detail
336     ensure
337       @target = old
338     end
339   end
340 end
skip_record?(record) click to toggle source

Should we skip the record? Basically, we skip text records. This is only here so subclasses can override it.

    # File lib/puppet/provider/parsedfile.rb
344 def self.skip_record?(record)
345   record_type(record[:record_type]).text?
346 end
target_object(target) click to toggle source

Initialize the object if necessary.

    # File lib/puppet/provider/parsedfile.rb
358 def self.target_object(target)
359   # only send the default mode if the actual provider defined it,
360   # because certain filetypes (e.g. the crontab variants) do not
361   # expect it in their initialize method
362   if default_mode
363     @target_objects[target] ||= filetype.new(target, default_mode)
364   else
365     @target_objects[target] ||= filetype.new(target)
366   end
367 
368   @target_objects[target]
369 end
target_records(target) click to toggle source

Find all of the records for a given target

    # File lib/puppet/provider/parsedfile.rb
372 def self.target_records(target)
373   @records.find_all { |r| r[:target] == target }
374 end
targets(resources = nil) click to toggle source

Find a list of all of the targets that we should be reading. This is used to figure out what targets we need to prefetch.

    # File lib/puppet/provider/parsedfile.rb
378 def self.targets(resources = nil)
379   targets = []
380   # First get the default target
381   raise Puppet::DevError, _("Parsed Providers must define a default target") unless self.default_target
382   targets << self.default_target
383 
384   # Then get each of the file objects
385   targets += @target_objects.keys
386 
387   # Lastly, check the file from any resource instances
388   if resources
389     resources.each do |name, resource|
390       value = resource.should(:target)
391       if value
392         targets << value
393       end
394     end
395   end
396 
397   targets.uniq.compact
398 end
to_file(records) click to toggle source

Compose file contents from the set of records.

If self.native_header_regex is not nil, possible vendor headers are identified by matching the return value against the expression. If one (or several consecutive) such headers, are found, they are either moved in front of the self.header if self.drop_native_header is false (this is the default), or removed from the return value otherwise.

@api private

Calls superclass method
    # File lib/puppet/provider/parsedfile.rb
409 def self.to_file(records)
410   text = super
411   if native_header_regex and (match = text.match(native_header_regex))
412     if drop_native_header
413       # concatenate the text in front of and after the native header
414       text = match.pre_match + match.post_match
415     else
416       native_header = match[0]
417       return native_header + header + match.pre_match + match.post_match
418     end
419   end
420   header + text
421 end

Public Instance Methods

create() click to toggle source
    # File lib/puppet/provider/parsedfile.rb
423 def create
424   @resource.class.validproperties.each do |property|
425     value = @resource.should(property)
426     if value
427       @property_hash[property] = value
428     end
429   end
430   mark_target_modified
431   (@resource.class.name.to_s + "_created").intern
432 end
destroy() click to toggle source
    # File lib/puppet/provider/parsedfile.rb
434 def destroy
435   # We use the method here so it marks the target as modified.
436   self.ensure = :absent
437   (@resource.class.name.to_s + "_deleted").intern
438 end
exists?() click to toggle source
    # File lib/puppet/provider/parsedfile.rb
440 def exists?
441   !(@property_hash[:ensure] == :absent or @property_hash[:ensure].nil?)
442 end
flush() click to toggle source

Write our data to disk.

    # File lib/puppet/provider/parsedfile.rb
445 def flush
446   # Make sure we've got a target and name set.
447 
448   # If the target isn't set, then this is our first modification, so
449   # mark it for flushing.
450   unless @property_hash[:target]
451     @property_hash[:target] = @resource.should(:target) || self.class.default_target
452     self.class.modified(@property_hash[:target])
453   end
454   @resource.class.key_attributes.each do |attr|
455     @property_hash[attr] ||= @resource[attr]
456   end
457 
458   self.class.flush(@property_hash)
459 end
prefetch() click to toggle source

Retrieve the current state from disk.

    # File lib/puppet/provider/parsedfile.rb
471 def prefetch
472   raise Puppet::DevError, _("Somehow got told to prefetch with no resource set") unless @resource
473   self.class.prefetch(@resource[:name] => @resource)
474 end
record_type() click to toggle source
    # File lib/puppet/provider/parsedfile.rb
476 def record_type
477   @property_hash[:record_type]
478 end

Private Instance Methods

mark_target_modified() click to toggle source

Mark both the resource and provider target as modified.

    # File lib/puppet/provider/parsedfile.rb
483 def mark_target_modified
484   restarget = @resource.should(:target) if defined?(@resource)
485   if restarget && restarget != @property_hash[:target]
486     self.class.modified(restarget)
487   end
488   self.class.modified(@property_hash[:target]) if @property_hash[:target] != :absent and @property_hash[:target]
489 end