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
90 def self.backup_target(target)
91   return nil unless target_object(target).respond_to?(:backup)
92 
93   @backup_stats ||= {}
94   return nil if @backup_stats[target] == @records.object_id
95 
96   target_object(target).backup
97   @backup_stats[target] = @records.object_id
98 end
clean(hash) click to toggle source
   # File lib/puppet/provider/parsedfile.rb
29 def self.clean(hash)
30   newhash = hash.dup
31   [:record_type, :on_disk].each do |p|
32     newhash.delete(p) if newhash.include?(p)
33   end
34 
35   newhash
36 end
clear() click to toggle source
   # File lib/puppet/provider/parsedfile.rb
38 def self.clear
39   @target_objects.clear
40   @records.clear
41 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
354 def self.default_mode
355   nil
356 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
150 def self.drop_native_header
151   false
152 end
filetype() click to toggle source
   # File lib/puppet/provider/parsedfile.rb
43 def self.filetype
44   @filetype ||= Puppet::Util::FileType.filetype(:flat)
45 end
filetype=(type) click to toggle source
   # File lib/puppet/provider/parsedfile.rb
47 def self.filetype=(type)
48   if type.is_a?(Class)
49     @filetype = type
50   else
51     klass = Puppet::Util::FileType.filetype(type)
52     if klass
53       @filetype = klass
54     else
55       raise ArgumentError, _("Invalid filetype %{type}") % { type: type }
56     end
57   end
58 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
63 def self.flush(record)
64   # Make sure this record is on the list to be flushed.
65   unless record[:on_disk]
66     record[:on_disk] = true
67     @records << record
68 
69     # If we've just added the record, then make sure our
70     # target will get flushed.
71     modified(record[:target] || default_target)
72   end
73 
74   return unless defined?(@modified) and ! @modified.empty?
75 
76   flushed = []
77   begin
78     @modified.sort_by(&:to_s).uniq.each do |target|
79       Puppet.debug "Flushing #{@resource_type.name} provider target #{target}"
80       flushed << target
81       flush_target(target)
82     end
83   ensure
84     @modified.reject! { |t| flushed.include?(t) }
85   end
86 end
flush_target(target) click to toggle source

Flush all of the records relating to a specific target.

    # File lib/puppet/provider/parsedfile.rb
101 def self.flush_target(target)
102   if @raise_prefetch_errors && @failed_prefetch_targets.key?(target)
103     raise Puppet::Error, _("Failed to read %{target}'s records when prefetching them. Reason: %{detail}") % { target: target, detail: @failed_prefetch_targets[target] }
104   end
105 
106   backup_target(target)
107 
108   records = target_records(target).reject { |r|
109     r[:ensure] == :absent
110   }
111 
112   target_object(target).write(to_file(records))
113 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
117   def self.header
118 %{# HEADER: This file was autogenerated at #{Time.now}
119 # HEADER: by puppet.  While it can still be managed manually, it
120 # HEADER: is definitely not recommended.\n}
121   end
initvars() click to toggle source

Add another type var.

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

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

    # File lib/puppet/provider/parsedfile.rb
171 def self.instances
172   targets.collect do |target|
173     prefetch_target(target)
174   end.flatten.reject { |r| skip_record?(r) }.collect do |record|
175     new(record)
176   end
177 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
237 def self.match_providers_with_resources(resources)
238   return unless resources
239   matchers = resources.dup
240   @records.each do |record|
241     # Skip things like comments and blank lines
242     next if skip_record?(record)
243 
244     if (resource = resource_for_record(record, resources))
245       resource.provider = new(record)
246     elsif respond_to?(:match)
247       resource = match(record, matchers)
248       if resource
249         matchers.delete(resource.title)
250         record[:name] = resource[:name]
251         resource.provider = new(record)
252       end
253     end
254   end
255 end
mk_resource_methods() click to toggle source

Override the default method with a lot more functionality.

    # File lib/puppet/provider/parsedfile.rb
180 def self.mk_resource_methods
181   [resource_type.validproperties, resource_type.parameters].flatten.each do |attr|
182     attr = attr.intern
183     define_method(attr) do
184       # If it's not a valid field for this record type (which can happen
185       # when different platforms support different fields), then just
186       # return the should value, so the resource shuts up.
187       if @property_hash[attr] or self.class.valid_attr?(self.class.name, attr)
188         @property_hash[attr] || :absent
189       else
190         if defined?(@resource)
191           @resource.should(attr)
192         else
193           nil
194         end
195       end
196     end
197 
198     define_method(attr.to_s + "=") do |val|
199       mark_target_modified
200       @property_hash[attr] = val
201     end
202   end
203 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
213 def self.modified(target)
214   @modified ||= []
215   @modified << target unless @modified.include?(target)
216 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
139 def self.native_header_regex
140   nil
141 end
new(record) click to toggle source
Calls superclass method Puppet::Provider::new
    # File lib/puppet/provider/parsedfile.rb
462 def initialize(record)
463   super
464 
465   # The 'record' could be a resource or a record, depending on how the provider
466   # is initialized.  If we got an empty property hash (probably because the resource
467   # is just being initialized), then we want to set up some defaults.
468   @property_hash = self.class.record?(resource[:name]) || {:record_type => self.class.name, :ensure => :absent} if @property_hash.empty?
469 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
225 def self.prefetch(resources = nil)
226   # Reset the record list.
227   @records = prefetch_all_targets(resources)
228 
229   match_providers_with_resources(resources)
230 end
prefetch_all_targets(resources) click to toggle source
    # File lib/puppet/provider/parsedfile.rb
272 def self.prefetch_all_targets(resources)
273   records = []
274   targets(resources).each do |target|
275     records += prefetch_target(target)
276   end
277   records
278 end
prefetch_target(target) click to toggle source

Prefetch an individual target.

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

Is there an existing record with this name?

    # File lib/puppet/provider/parsedfile.rb
315 def self.record?(name)
316   return nil unless @records
317   @records.find { |r| r[:name] == name }
318 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
265 def self.resource_for_record(record, resources)
266   name = record[:name]
267   if name
268     resources[name]
269   end
270 end
resource_type=(resource) click to toggle source

Always make the resource methods.

Calls superclass method
    # File lib/puppet/provider/parsedfile.rb
206 def self.resource_type=(resource)
207   super
208   mk_resource_methods
209 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
322 def self.retrieve(path)
323   # XXX We need to be doing something special here in case of failure.
324   text = target_object(path).read
325   if text.nil? or text == ""
326     # there is no file
327     return []
328   else
329     # Set the target, for logging.
330     old = @target
331     begin
332       @target = path
333       return self.parse(text)
334     rescue Puppet::Error => detail
335       detail.file = @target if detail.respond_to?(:file=)
336       raise detail
337     ensure
338       @target = old
339     end
340   end
341 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
345 def self.skip_record?(record)
346   record_type(record[:record_type]).text?
347 end
target_object(target) click to toggle source

Initialize the object if necessary.

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

Find all of the records for a given target

    # File lib/puppet/provider/parsedfile.rb
373 def self.target_records(target)
374   @records.find_all { |r| r[:target] == target }
375 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
379 def self.targets(resources = nil)
380   targets = []
381   # First get the default target
382   raise Puppet::DevError, _("Parsed Providers must define a default target") unless self.default_target
383   targets << self.default_target
384 
385   # Then get each of the file objects
386   targets += @target_objects.keys
387 
388   # Lastly, check the file from any resource instances
389   if resources
390     resources.each do |name, resource|
391       value = resource.should(:target)
392       if value
393         targets << value
394       end
395     end
396   end
397 
398   targets.uniq.compact
399 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
410 def self.to_file(records)
411   text = super
412   if native_header_regex and (match = text.match(native_header_regex))
413     if drop_native_header
414       # concatenate the text in front of and after the native header
415       text = match.pre_match + match.post_match
416     else
417       native_header = match[0]
418       return native_header + header + match.pre_match + match.post_match
419     end
420   end
421   header + text
422 end

Public Instance Methods

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

Write our data to disk.

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

Retrieve the current state from disk.

    # File lib/puppet/provider/parsedfile.rb
472 def prefetch
473   raise Puppet::DevError, _("Somehow got told to prefetch with no resource set") unless @resource
474   self.class.prefetch(@resource[:name] => @resource)
475 end
record_type() click to toggle source
    # File lib/puppet/provider/parsedfile.rb
477 def record_type
478   @property_hash[:record_type]
479 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
484 def mark_target_modified
485   restarget = @resource.should(:target) if defined?(@resource)
486   if restarget && restarget != @property_hash[:target]
487     self.class.modified(restarget)
488   end
489   self.class.modified(@property_hash[:target]) if @property_hash[:target] != :absent and @property_hash[:target]
490 end