Class: PDK::Module::TemplateDir

Inherits:
Object
  • Object
show all
Defined in:
lib/pdk/module/templatedir.rb

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(uri, module_metadata = {}, init = false) {|self| ... } ⇒ TemplateDir

Initialises the TemplateDir object with the path or URL to the template and the block of code to run to be run while the template is available.

The template directory is only guaranteed to be available on disk within the scope of the block passed to this method.

template or a URI to a git repository. Defaults to an empty Hash. the template available on disk.

Examples:

Using a git repository as a template

PDK::Module::TemplateDir.new('https://github.com/puppetlabs/pdk-templates') do |t|
  t.render do |filename, content|
    File.open(filename, 'w') do |file|
      file.write(content)
    end
  end
end

Parameters:

  • uri (PDK::Util::TemplateURI)

    The path to a directory to use as the

  • module_metadata (Hash) (defaults to: {})

    A Hash containing the module metadata.

Yield Parameters:

Raises:

  • (ArgumentError)

    If no block is given to this method.



36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/pdk/module/templatedir.rb', line 36

def initialize(uri,  = {}, init = false)
  require 'pdk/analytics'
  require 'pdk/util/template_uri'
  require 'pdk/util/git'

  unless block_given?
    raise ArgumentError, _('%{class_name} must be initialized with a block.') % { class_name: self.class.name }
  end
  unless uri.is_a? PDK::Util::TemplateURI
    raise ArgumentError, _('PDK::Module::TemplateDir.new must be initialized with a PDK::Util::TemplateURI, got a %{uri_type}') % { uri_type: uri.class }
  end

  if PDK::Util::Git.repo?(uri.git_remote)
    # This is either a bare local repo or a remote. either way it needs cloning.
    @path = clone_template_repo(uri)
    temp_dir_clone = true
  else
    # if it is a local path & non-bare repo then we can use it directly.
    # Still have to check the branch.
    @path = uri.shell_path
    # We don't do a checkout of local-path repos. There are lots of edge
    # cases or user un-expectations.
    if PDK::Util::Git.work_tree?(@path)
      PDK.logger.warn _("Repository '%{repo}' has a work-tree; skipping git reset.") % {
        repo: @path,
      }
    end
  end
  @uri = uri

  @init = init
  @moduleroot_dir = File.join(@path, 'moduleroot')
  @moduleroot_init = File.join(@path, 'moduleroot_init')
  @dirs = [@moduleroot_dir]
  @dirs << @moduleroot_init if @init
  @object_dir = File.join(@path, 'object_templates')

  validate_module_template!

  @module_metadata = 

  template_type = uri.default? ? 'default' : 'custom'
  PDK.analytics.event('TemplateDir', 'initialize', label: template_type)

  yield self
ensure
  # If we cloned a git repo to get the template, remove the clone once
  # we're done with it.
  if temp_dir_clone
    require 'fileutils'
    FileUtils.remove_dir(@path)
  end
end

Instance Attribute Details

#module_metadataObject

Returns the value of attribute module_metadata.



6
7
8
# File 'lib/pdk/module/templatedir.rb', line 6

def 
  @module_metadata
end

#uriObject (readonly)

Returns the value of attribute uri.



7
8
9
# File 'lib/pdk/module/templatedir.rb', line 7

def uri
  @uri
end

Class Method Details

.files_in_template(dirs) ⇒ Hash{String=>String}

Get a list of template files in the template directory.

value locations.

Returns:

  • (Hash{String=>String})

    A hash of key file names and



241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/pdk/module/templatedir.rb', line 241

def self.files_in_template(dirs)
  temp_paths = []
  dirlocs = []
  dirs.each do |dir|
    raise ArgumentError, _("The directory '%{dir}' doesn't exist") % { dir: dir } unless Dir.exist?(dir)
    temp_paths += Dir.glob(File.join(dir, '**', '*'), File::FNM_DOTMATCH).select do |template_path|
      if File.file?(template_path) && !File.symlink?(template_path)
        dirlocs << dir
      end
    end
    temp_paths.map do |template_path|
      template_path.sub!(%r{\A#{Regexp.escape(dir)}#{Regexp.escape(File::SEPARATOR)}}, '')
    end
  end
  Hash[temp_paths.zip dirlocs]
end

Instance Method Details

#cache_template_ref(path, ref = nil) ⇒ Object



384
385
386
387
388
# File 'lib/pdk/module/templatedir.rb', line 384

def cache_template_ref(path, ref = nil)
  require 'pdk/util/git'

  @template_ref ||= PDK::Util::Git.describe(File.join(path, '.git'), ref)
end

#checkout_template_ref(path, ref) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
# File 'lib/pdk/module/templatedir.rb', line 365

def checkout_template_ref(path, ref)
  require 'pdk/util/git'

  if PDK::Util::Git.work_dir_clean?(path)
    Dir.chdir(path) do
      full_ref = PDK::Util::Git.ls_remote(path, ref)
      cache_template_ref(path, full_ref)
      reset_result = PDK::Util::Git.git('reset', '--hard', full_ref)
      return if reset_result[:exit_code].zero?

      PDK.logger.error reset_result[:stdout]
      PDK.logger.error reset_result[:stderr]
      raise PDK::CLI::FatalError, _("Unable to checkout '%{ref}' of git repository at '%{path}'.") % { ref: ref, path: path }
    end
  else
    PDK.logger.warn _("Uncommitted changes found when attempting to checkout '%{ref}' of git repository at '%{path}'; skipping git reset.") % { ref: ref, path: path }
  end
end

#clone_template_repo(uri) ⇒ String

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns Path to working directory into which template repo has been cloned and reset.

Returns:

  • (String)

    Path to working directory into which template repo has been cloned and reset

Raises:



340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
# File 'lib/pdk/module/templatedir.rb', line 340

def clone_template_repo(uri)
  # @todo When switching this over to using rugged, cache the cloned
  # template repo in `%AppData%` or `$XDG_CACHE_DIR` and update before
  # use.
  require 'pdk/util'
  require 'pdk/util/git'

  temp_dir = PDK::Util.make_tmpdir_name('pdk-templates')
  origin_repo = uri.git_remote
  git_ref = uri.git_ref

  clone_result = PDK::Util::Git.git('clone', origin_repo, temp_dir)

  if clone_result[:exit_code].zero?
    checkout_template_ref(temp_dir, git_ref)
  else
    PDK.logger.error clone_result[:stdout]
    PDK.logger.error clone_result[:stderr]
    raise PDK::CLI::FatalError, _("Unable to clone git repository at '%{repo}' into '%{dest}'.") % { repo: origin_repo, dest: temp_dir }
  end

  PDK::Util.canonical_path(temp_dir)
end

#config_for(dest_path, sync_config_path = nil) ⇒ Hash

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Generate a hash of data to be used when rendering the specified template.

data is for, relative to the root of the module.

‘@configs` instance variable.

Parameters:

  • dest_path (String)

    The destination path of the file that the

Returns:

  • (Hash)

    The data that will be available to the template via the



268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
# File 'lib/pdk/module/templatedir.rb', line 268

def config_for(dest_path, sync_config_path = nil)
  require 'pdk/util'
  require 'pdk/analytics'

  module_root = PDK::Util.module_root
  sync_config_path ||= File.join(module_root, '.sync.yml') unless module_root.nil?
  config_path = File.join(@path, 'config_defaults.yml')

  if @config.nil?
    require 'deep_merge'
    conf_defaults = read_config(config_path)
    @sync_config = read_config(sync_config_path) unless sync_config_path.nil?
    @config = conf_defaults
    @config.deep_merge!(@sync_config, knockout_prefix: '---') unless @sync_config.nil?
  end
  file_config = @config.fetch(:global, {})
  file_config['module_metadata'] = @module_metadata
  file_config.merge!(@config.fetch(dest_path, {})) unless dest_path.nil?
  file_config.merge!(@config).tap do |c|
    if uri.default?
      file_value = if c['unmanaged']
                     'unmanaged'
                   elsif c['delete']
                     'deleted'
                   elsif @sync_config && @sync_config.key?(dest_path)
                     'customized'
                   else
                     'default'
                   end

      PDK.analytics.event('TemplateDir', 'file', label: dest_path, value: file_value)
    end
  end
end

#metadataHash{String => String}

Retrieve identifying metadata for the template.

For git repositories, this will return the URL to the repository and a reference to the HEAD.

Returns:

  • (Hash{String => String})

    A hash of identifying metadata.



98
99
100
101
102
103
104
105
106
# File 'lib/pdk/module/templatedir.rb', line 98

def 
  require 'pdk/util/version'

  {
    'pdk-version'  => PDK::Util::Version.version_string,
    'template-url' => uri.,
    'template-ref' => cache_template_ref(@path),
  }
end

#object_configHash

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Generate a hash of data to be used when rendering object templates.

Read ‘config_defaults.yml` from the root of the template directory (if it exists) build a hash of values from the value of the `:global` key.

‘@configs` instance variable.

Returns:

  • (Hash)

    The data that will be available to the template via the



198
199
200
# File 'lib/pdk/module/templatedir.rb', line 198

def object_config
  config_for(nil)
end

#object_template_for(object_type) ⇒ Hash{Symbol => String}

Searches the template directory for template files that can be used to render files for the specified object type.

‘:defined_type`, `:fact`, etc).

template dir, otherwise ‘nil`. The returned hash can contain two keys, :object contains the path on disk to the template for the object, :spec contains the path on disk to the template for the object’s spec file (if available).

Parameters:

  • object_type (Symbol)

    The object type, e.g. (‘:class`,

Returns:

  • (Hash{Symbol => String})

    if the templates are available in the



169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
# File 'lib/pdk/module/templatedir.rb', line 169

def object_template_for(object_type)
  object_path = File.join(@object_dir, "#{object_type}.erb")
  type_path = File.join(@object_dir, "#{object_type}_type.erb")
  device_path = File.join(@object_dir, "#{object_type}_device.erb")
  spec_path = File.join(@object_dir, "#{object_type}_spec.erb")
  type_spec_path = File.join(@object_dir, "#{object_type}_type_spec.erb")

  if File.file?(object_path) && File.readable?(object_path)
    result = { object: object_path }
    result[:type] = type_path if File.file?(type_path) && File.readable?(type_path)
    result[:spec] = spec_path if File.file?(spec_path) && File.readable?(spec_path)
    result[:device] = device_path if File.file?(device_path) && File.readable?(device_path)
    result[:type_spec] = type_spec_path if File.file?(type_spec_path) && File.readable?(type_spec_path)
    result
  else
    nil
  end
end

#read_config(loc) ⇒ Hash

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Generates a hash of data from a given yaml file location.

if so.

Parameters:

  • loc (String)

    The path of the yaml config file.

Returns:

  • (Hash)

    The data that has been read in from the given yaml file.



313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
# File 'lib/pdk/module/templatedir.rb', line 313

def read_config(loc)
  if File.file?(loc) && File.readable?(loc)
    require 'yaml'

    begin
      YAML.safe_load(File.read(loc), [], [], true)
    rescue Psych::SyntaxError => e
      PDK.logger.warn _("'%{file}' is not a valid YAML file: %{problem} %{context} at line %{line} column %{column}") % {
        file:    loc,
        problem: e.problem,
        context: e.context,
        line:    e.line,
        column:  e.column,
      }
      {}
    end
  else
    {}
  end
end

#render {|dest_path, dest_content| ... } ⇒ void

This method returns an undefined value.

Loop through the files in the template, yielding each rendered file to the supplied block.

relative to the root of the module. destination file.

Yield Parameters:

  • dest_path (String)

    The path of the destination file,

  • dest_content (String)

    The rendered content of the

Raises:



121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
# File 'lib/pdk/module/templatedir.rb', line 121

def render
  require 'pdk/template_file'

  PDK::Module::TemplateDir.files_in_template(@dirs).each do |template_file, template_loc|
    template_file = template_file.to_s
    PDK.logger.debug(_("Rendering '%{template}'...") % { template: template_file })
    dest_path = template_file.sub(%r{\.erb\Z}, '')
    config = config_for(dest_path)

    dest_status = if template_loc.start_with?(@moduleroot_init)
                    :init
                  else
                    :manage
                  end

    if config['unmanaged']
      dest_status = :unmanage
    elsif config['delete']
      dest_status = :delete
    else
      begin
        dest_content = PDK::TemplateFile.new(File.join(template_loc, template_file), configs: config, template_dir: self).render
      rescue => error
        error_msg = _(
          "Failed to render template '%{template}'\n" \
          '%{exception}: %{message}',
        ) % { template: template_file, exception: error.class, message: error.message }
        raise PDK::CLI::FatalError, error_msg
      end
    end

    yield dest_path, dest_content, dest_status
  end
end

#validate_module_template!void

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method returns an undefined value.

Validate the content of the template directory.

a directory called ‘moduleroot’.

Raises:

  • (ArgumentError)

    If the specified path is not a directory.

  • (ArgumentError)

    If the template directory does not contain



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
# File 'lib/pdk/module/templatedir.rb', line 211

def validate_module_template!
  # rubocop:disable Style/GuardClause
  unless File.directory?(@path)
    require 'pdk/util'

    if PDK::Util.package_install? && File.fnmatch?(File.join(PDK::Util.package_cachedir, '*'), @path)
      raise ArgumentError, _('The built-in template has substantially changed. Please run "pdk convert" on your module to continue.')
    else
      raise ArgumentError, _("The specified template '%{path}' is not a directory.") % { path: @path }
    end
  end

  unless File.directory?(@moduleroot_dir)
    raise ArgumentError, _("The template at '%{path}' does not contain a 'moduleroot/' directory.") % { path: @path }
  end

  unless File.directory?(@moduleroot_init)
    # rubocop:disable Metrics/LineLength
    raise ArgumentError, _("The template at '%{path}' does not contain a 'moduleroot_init/' directory, which indicates you are using an older style of template. Before continuing please use the --template-url flag when running the pdk new commands to pass a new style template.") % { path: @path }
    # rubocop:enable Metrics/LineLength Style/GuardClause
  end
  # rubocop:enable Style/GuardClause
end