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.



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 41

def initialize(uri,  = {}, init = false)
  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
    FileUtils.remove_dir(@path)
  end
end

Instance Attribute Details

#module_metadataObject

Returns the value of attribute module_metadata.



11
12
13
# File 'lib/pdk/module/templatedir.rb', line 11

def 
  @module_metadata
end

#uriObject (readonly)

Returns the value of attribute uri.



12
13
14
# File 'lib/pdk/module/templatedir.rb', line 12

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



235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
# File 'lib/pdk/module/templatedir.rb', line 235

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



367
368
369
# File 'lib/pdk/module/templatedir.rb', line 367

def cache_template_ref(path, ref = nil)
  @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.



350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
# File 'lib/pdk/module/templatedir.rb', line 350

def checkout_template_ref(path, ref)
  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:



328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
# File 'lib/pdk/module/templatedir.rb', line 328

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



262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
# File 'lib/pdk/module/templatedir.rb', line 262

def config_for(dest_path, sync_config_path = nil)
  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?
    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
# File 'lib/pdk/module/templatedir.rb', line 98

def 
  {
    '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



194
195
196
# File 'lib/pdk/module/templatedir.rb', line 194

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



165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/pdk/module/templatedir.rb', line 165

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.



303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
# File 'lib/pdk/module/templatedir.rb', line 303

def read_config(loc)
  if File.file?(loc) && File.readable?(loc)
    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:



119
120
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
# File 'lib/pdk/module/templatedir.rb', line 119

def render
  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



207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/pdk/module/templatedir.rb', line 207

def validate_module_template!
  # rubocop:disable Style/GuardClause
  unless File.directory?(@path)
    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