Module: PuppetX::FileMapper::ClassMethods

Defined in:
lib/puppetx/filemapper.rb

Instance Attribute Summary collapse

Instance Method Summary collapse

Instance Attribute Details

#filetypeSymbol

Returns The FileType to use when interacting with target files.

Returns:

  • (Symbol)

    The FileType to use when interacting with target files



60
61
62
# File 'lib/puppetx/filemapper.rb', line 60

def filetype
  @filetype
end

#mapped_filesObject (readonly)

Returns the value of attribute mapped_files.



66
67
68
# File 'lib/puppetx/filemapper.rb', line 66

def mapped_files
  @mapped_files
end

Returns Whether empty files will be removed.

Returns:

  • (TrueClass || FalseClass)

    Whether empty files will be removed



56
57
58
# File 'lib/puppetx/filemapper.rb', line 56

def unlink_empty_files
  @unlink_empty_files
end

Instance Method Details

#collect_providers_for_file(filename) ⇒ Array<Puppet::Provider>

Generate an array of providers that should be flushed to a specific file

Only providers that should be present will be returned regardless of the containing file.

Parameters:

  • filename (String)

    The name of the file to find providers for

Returns:

  • (Array<Puppet::Provider>)


234
235
236
237
238
# File 'lib/puppetx/filemapper.rb', line 234

def collect_providers_for_file(filename)
  @all_providers.select do |provider|
    provider.select_file == filename && provider.ensure == :present
  end
end

#dirty_file!(filename) ⇒ Object



240
241
242
# File 'lib/puppetx/filemapper.rb', line 240

def dirty_file!(filename)
  @mapped_files[filename][:dirty] = true
end

#failed!Object



81
82
83
# File 'lib/puppetx/filemapper.rb', line 81

def failed!
  @failed = true
end

#failed?Boolean

Returns:

  • (Boolean)


77
78
79
# File 'lib/puppetx/filemapper.rb', line 77

def failed?
  @failed
end

#flush_file(filename) ⇒ Object

Flush provider instances associated with the given file and call any defined hooks

If the provider is in a failure state, the provider class will refuse to flush any file, since we’re in an unknown state.

This method respects two method hooks: ‘pre_flush_hook` and `post_flush_hook`. These methods must accept one argument, the path of the file being flushed. `post_flush_hook` is guaranteed to be called after the flush has occurred.

Parameters:

  • filename (String)

    The path of the file to be flushed



254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
# File 'lib/puppetx/filemapper.rb', line 254

def flush_file(filename)
  if failed?
    err "#{name} is in an error state, refusing to flush file #{filename}"
    return
  end

  if !@mapped_files[filename][:dirty]
    Puppet.debug "#{name} was requested to flush the file #{filename}, but it was not marked as dirty - doing nothing."
  else
    # Collect all providers that should be present and pass them to the
    # including class for formatting.
    target_providers = collect_providers_for_file(filename)
    file_contents = format_file(filename, target_providers)

    raise Puppet::DevError, "expected #{self}.format_file to return a String, got a #{file_contents.class}" unless file_contents.is_a? String

    # Call the `pre_flush_hook` method if it's defined
    pre_flush_hook(filename) if respond_to? :pre_flush_hook

    begin
      file_contents.empty? && unlink_empty_files ? remove_empty_file(filename) : perform_write(filename, file_contents)
    ensure
      post_flush_hook(filename) if respond_to? :post_flush_hook
    end
  end
rescue
  # If something failed during the flush process, mark the provider as failed. There's not
  # much we can do about any file that's already been flushed but we can stop smashing things.
  @failed = true
  raise
end

#initvarsObject



68
69
70
71
72
73
74
75
# File 'lib/puppetx/filemapper.rb', line 68

def initvars
  super
  @mapped_files = Hash.new { |h, k| h[k] = {} }
  @unlink_empty_files = false
  @filetype = :flat
  @failed = false
  @all_providers = []
end

#instancesArray<Puppet::Provider>

Returns all instances of the provider using this mixin.

Returns:

  • (Array<Puppet::Provider>)


101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/puppetx/filemapper.rb', line 101

def instances
  provider_hashes = load_all_providers_from_disk

  provider_hashes.map do |h|
    h[:provider] = name
    h[:ensure] = :present
    new(h)
  end
rescue
  # If something failed while loading instances, mark the provider class
  # as failed and pass the exception along
  failed!
  raise
end

#load_all_providers_from_diskArray<Hash<String, Hash<Symbol, Object>>>

Reads all files from disk and returns an array of hashes representing provider instances.

Examples:

IncludingProvider.load_all_providers_from_disk
# => [
#   { "/path/to/file" => {
#     :dirty    => false,
#     :filetype => #<Puppet::Util::FileTypeFlat:0x007fbf5b05ff10>,
#   },
#   { "/path/to/another/file" => {
#     :dirty    => false,
#     :filetype => #<Puppet::Util::FileTypeFlat:0x007fbf5b05c108,
#   },
#

Returns:

  • (Array<Hash<String, Hash<Symbol, Object>>>)

    An array containing a set of hashes, keyed with a file path and values being a hash containg the state of the file and the filetype associated with it.



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
# File 'lib/puppetx/filemapper.rb', line 153

def load_all_providers_from_disk
  validate_class!

  # Retrieve a list of files to fetch, and cache a copy of a filetype
  # for each one
  target_files.each do |file|
    @mapped_files[file][:filetype] = Puppet::Util::FileType.filetype(filetype).new(file)
    @mapped_files[file][:dirty]    = false
  end

  # Read and parse each file.
  provider_hashes = []
  @mapped_files.each_pair do |filename, file_attrs|
    # The filetype path must be a type pathname, or it must respond to to_str
    # If it doesn't meet these criteria go to next file
    path = file_attrs[:filetype].path

    next unless path.is_a?(Pathname) || path.respond_to?(:to_str)
    arr = parse_file(filename, file_attrs[:filetype].read)
    unless arr.is_a? Array
      raise Puppet::DevError, "expected #{self}.parse_file to return an Array, got a #{arr.class}"
    end
    provider_hashes.concat arr
  end
  provider_hashes
end

#mk_property_methodsObject

Create attr_accessors for properties and mark the provider as dirty on change.



205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
# File 'lib/puppetx/filemapper.rb', line 205

def mk_property_methods
  resource_type.validproperties.each do |attr|
    attr = attr.intern if attr.respond_to?(:intern) && !attr.is_a?(Symbol)

    # Generate the attr_reader method
    define_method(attr) do
      if @property_hash[attr].nil?
        :absent
      else
        @property_hash[attr]
      end
    end

    # Generate the attr_writer and have it mark the resource as dirty when called
    define_method("#{attr}=") do |val|
      @property_hash[attr] = val
      dirty!
    end
  end
end

#new(*args) ⇒ Object

Register all provider instances with the class

In order to flush all provider instances to a given file, we need to be able to track them all. When provider#flush is called and the file associated with that provider instance is dirty, the file needs to be flushed and all provider instances associated with that file will be passed to self.flush_file



92
93
94
95
96
# File 'lib/puppetx/filemapper.rb', line 92

def new(*args)
  obj = super
  @all_providers << obj
  obj
end

#perform_write(filename, contents) ⇒ Object

We have a dirty file and the new contents ready, back up the file and perform the flush.

Parameters:

  • filename (String)

    The destination filename

  • contents (String)

    The new file contents



290
291
292
293
294
295
296
# File 'lib/puppetx/filemapper.rb', line 290

def perform_write(filename, contents)
  @mapped_files[filename][:filetype] ||= Puppet::Util::FileType.filetype(filetype).new(filename)
  filetype = @mapped_files[filename][:filetype]

  filetype.backup if filetype.respond_to? :backup
  filetype.write(contents)
end

#prefetch(resources = {}) ⇒ Object

Match up all resources that have existing providers.

Pass over all provider instances, and see if there is a resource with the same namevar as a provider instance. If such a resource exists, set the provider field of that resource to the existing provider.

This is a hook method that will be called by Puppet::Transaction#prefetch

Parameters:

  • resources (Hash<String, Puppet::Resource>) (defaults to: {})


189
190
191
192
193
194
195
196
197
198
199
200
201
202
# File 'lib/puppetx/filemapper.rb', line 189

def prefetch(resources = {})
  # generate hash of {provider_name => provider}
  providers = instances.each_with_object({}) do |instance, hash|
    # name is not necessarily the name_var.
    # n.b. the following will fall flat for composite namevars.
    namevar = resource_type.key_attributes.first
    hash[instance.send(namevar)] = instance
  end

  # For each prefetched resource, try to match it to a provider
  resources.each_pair do |resource_name, resource|
    resource.provider = providers[resource_name] if providers[resource_name]
  end
end

#remove_empty_file(filename) ⇒ Object

Back up and remove a file, if it exists

Parameters:

  • filename (String)

    The file to remove



301
302
303
304
305
306
307
308
309
# File 'lib/puppetx/filemapper.rb', line 301

def remove_empty_file(filename)
  return unless File.exist? filename
  @mapped_files[filename][:filetype] ||= Puppet::Util::FileType.filetype(filetype).new(filename)
  filetype = @mapped_files[filename][:filetype]

  filetype.backup if filetype.respond_to? :backup

  File.unlink(filename)
end

#validate_class!Object

Validate that the required methods are available.

Raises:

  • Puppet::DevError if an expected method is unavailable



119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/puppetx/filemapper.rb', line 119

def validate_class!
  required_class_hooks    = [:target_files, :parse_file, :format_file]
  required_instance_hooks = [:select_file]

  required_class_hooks.each do |method|
    raise Puppet::DevError, "#{self} has not implemented `self.#{method}`" unless respond_to? method
  end

  required_instance_hooks.each do |method|
    raise Puppet::DevError, "#{self} has not implemented `##{method}`" unless method_defined? method
  end
end