pdk 1.14.1 → 1.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +27 -0
  3. data/lib/pdk/answer_file.rb +5 -7
  4. data/lib/pdk/cli.rb +1 -0
  5. data/lib/pdk/cli/console.rb +1 -1
  6. data/lib/pdk/cli/convert.rb +10 -2
  7. data/lib/pdk/cli/exec.rb +2 -1
  8. data/lib/pdk/cli/module/build.rb +1 -1
  9. data/lib/pdk/cli/module/generate.rb +1 -1
  10. data/lib/pdk/cli/release.rb +192 -0
  11. data/lib/pdk/cli/release/prep.rb +39 -0
  12. data/lib/pdk/cli/release/publish.rb +40 -0
  13. data/lib/pdk/cli/update.rb +12 -0
  14. data/lib/pdk/config.rb +1 -1
  15. data/lib/pdk/config/namespace.rb +1 -1
  16. data/lib/pdk/generate/module.rb +11 -17
  17. data/lib/pdk/generate/puppet_object.rb +1 -2
  18. data/lib/pdk/generate/task.rb +1 -1
  19. data/lib/pdk/module.rb +2 -1
  20. data/lib/pdk/module/build.rb +15 -25
  21. data/lib/pdk/module/convert.rb +4 -9
  22. data/lib/pdk/module/metadata.rb +1 -3
  23. data/lib/pdk/module/release.rb +260 -0
  24. data/lib/pdk/module/template_dir.rb +115 -0
  25. data/lib/pdk/module/template_dir/base.rb +268 -0
  26. data/lib/pdk/module/template_dir/git.rb +91 -0
  27. data/lib/pdk/module/template_dir/local.rb +21 -0
  28. data/lib/pdk/module/update.rb +17 -5
  29. data/lib/pdk/module/update_manager.rb +1 -1
  30. data/lib/pdk/report.rb +18 -12
  31. data/lib/pdk/report/event.rb +6 -3
  32. data/lib/pdk/template_file.rb +2 -2
  33. data/lib/pdk/util.rb +17 -6
  34. data/lib/pdk/util/bundler.rb +8 -9
  35. data/lib/pdk/util/changelog_generator.rb +115 -0
  36. data/lib/pdk/util/filesystem.rb +62 -2
  37. data/lib/pdk/util/git.rb +60 -8
  38. data/lib/pdk/util/puppet_version.rb +4 -5
  39. data/lib/pdk/util/ruby_version.rb +3 -3
  40. data/lib/pdk/util/template_uri.rb +49 -40
  41. data/lib/pdk/util/version.rb +4 -4
  42. data/lib/pdk/validate/metadata/metadata_syntax.rb +2 -2
  43. data/lib/pdk/validate/puppet/puppet_epp.rb +2 -4
  44. data/lib/pdk/validate/puppet/puppet_syntax.rb +2 -4
  45. data/lib/pdk/validate/tasks/metadata_lint.rb +2 -2
  46. data/lib/pdk/validate/yaml/syntax.rb +3 -3
  47. data/lib/pdk/version.rb +1 -1
  48. data/locales/pdk.pot +401 -149
  49. metadata +11 -3
  50. data/lib/pdk/module/templatedir.rb +0 -391
@@ -0,0 +1,115 @@
1
+ require 'pdk'
2
+
3
+ module PDK
4
+ module Module
5
+ module TemplateDir
6
+ # Creates a TemplateDir object with the path or URL to the template
7
+ # and the block of code to run to be run while the template is available.
8
+ #
9
+ # The template directory is only guaranteed to be available on disk
10
+ # within the scope of the block passed to this method.
11
+ #
12
+ # @param uri [PDK::Util::TemplateURI] The path to a directory to use as the
13
+ # template or a URI to a git repository.
14
+ # @param module_metadata [Hash] A Hash containing the module metadata.
15
+ # Defaults to an empty Hash.
16
+ # @yieldparam self [PDK::Module::TemplateDir] The initialised object with
17
+ # the template available on disk.
18
+ #
19
+ # @example Using a git repository as a template
20
+ # PDK::Module::TemplateDir.with('https://github.com/puppetlabs/pdk-templates') do |t|
21
+ # t.render do |filename, content|
22
+ # File.open(filename, 'w') do |file|
23
+ # file.write(content)
24
+ # end
25
+ # end
26
+ # end
27
+ #
28
+ # @raise [ArgumentError] If no block is given to this method.
29
+ # @raise [PDK::CLI::FatalError] (see #clone_repo)
30
+ # @raise [ArgumentError] (see #validate_module_template!)
31
+ #
32
+ # @api public
33
+ def self.with(uri, module_metadata = {}, init = false)
34
+ unless block_given?
35
+ raise ArgumentError, _('%{class_name}.with must be passed a block.') % { class_name: name }
36
+ end
37
+ unless uri.is_a? PDK::Util::TemplateURI
38
+ raise ArgumentError, _('%{class_name}.with must be passed a PDK::Util::TemplateURI, got a %{uri_type}') % { uri_type: uri.class, class_name: name }
39
+ end
40
+
41
+ if PDK::Util::Git.repo?(uri.bare_uri)
42
+ require 'pdk/module/template_dir/git'
43
+ PDK::Module::TemplateDir::Git.new(uri, module_metadata, init) { |value| yield value }
44
+ else
45
+ require 'pdk/module/template_dir/local'
46
+ PDK::Module::TemplateDir::Local.new(uri, module_metadata, init) { |value| yield value }
47
+ end
48
+ end
49
+
50
+ def self.moduleroot_dir(template_root_dir)
51
+ File.join(template_root_dir, 'moduleroot')
52
+ end
53
+
54
+ def self.moduleroot_init(template_root_dir)
55
+ File.join(template_root_dir, 'moduleroot_init')
56
+ end
57
+
58
+ # Validate the content of the template directory.
59
+ #
60
+ # @raise [ArgumentError] If the specified path is not a directory.
61
+ # @raise [ArgumentError] If the template directory does not contain
62
+ # a directory called 'moduleroot'.
63
+ #
64
+ # @return [void]
65
+ #
66
+ # @api public
67
+ def self.validate_module_template!(template_root_dir)
68
+ # rubocop:disable Style/GuardClause
69
+ unless PDK::Util::Filesystem.directory?(template_root_dir)
70
+ require 'pdk/util'
71
+
72
+ if PDK::Util.package_install? && PDK::Util::Filesystem.fnmatch?(File.join(PDK::Util.package_cachedir, '*'), template_root_dir)
73
+ raise ArgumentError, _('The built-in template has substantially changed. Please run "pdk convert" on your module to continue.')
74
+ else
75
+ raise ArgumentError, _("The specified template '%{path}' is not a directory.") % { path: template_root_dir }
76
+ end
77
+ end
78
+
79
+ unless PDK::Util::Filesystem.directory?(moduleroot_dir(template_root_dir))
80
+ raise ArgumentError, _("The template at '%{path}' does not contain a 'moduleroot/' directory.") % { path: template_root_dir }
81
+ end
82
+
83
+ unless PDK::Util::Filesystem.directory?(moduleroot_init(template_root_dir))
84
+ # rubocop:disable Metrics/LineLength
85
+ 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: template_root_dir }
86
+ # rubocop:enable Metrics/LineLength
87
+ end
88
+ # rubocop:enable Style/GuardClause
89
+ end
90
+
91
+ # Get a list of template files in the template directory.
92
+ #
93
+ # @return [Hash{String=>String}] A hash of key file names and
94
+ # value locations.
95
+ #
96
+ # @api public
97
+ def self.files_in_template(dirs)
98
+ temp_paths = []
99
+ dirlocs = []
100
+ dirs.each do |dir|
101
+ raise ArgumentError, _("The directory '%{dir}' doesn't exist") % { dir: dir } unless PDK::Util::Filesystem.directory?(dir)
102
+ temp_paths += PDK::Util::Filesystem.glob(File.join(dir, '**', '*'), File::FNM_DOTMATCH).select do |template_path|
103
+ if PDK::Util::Filesystem.file?(template_path) && !PDK::Util::Filesystem.symlink?(template_path)
104
+ dirlocs << dir
105
+ end
106
+ end
107
+ temp_paths.map do |template_path|
108
+ template_path.sub!(%r{\A#{Regexp.escape(dir)}#{Regexp.escape(File::SEPARATOR)}}, '')
109
+ end
110
+ end
111
+ Hash[temp_paths.zip dirlocs]
112
+ end
113
+ end
114
+ end
115
+ end
@@ -0,0 +1,268 @@
1
+ require 'pdk'
2
+
3
+ module PDK
4
+ module Module
5
+ module TemplateDir
6
+ class Base
7
+ attr_accessor :module_metadata
8
+ attr_reader :uri
9
+
10
+ # Initialises the TemplateDir object with the path or URL to the template
11
+ # and the block of code to run to be run while the template is available.
12
+ #
13
+ # The template directory is only guaranteed to be available on disk
14
+ # within the scope of the block passed to this method.
15
+ #
16
+ # @param uri [PDK::Util::TemplateURI] The path to a directory to use as the
17
+ # template or a URI to a git repository.
18
+ # @param module_metadata [Hash] A Hash containing the module metadata.
19
+ # Defaults to an empty Hash.
20
+ # @yieldparam self [PDK::Module::TemplateDir] The initialised object with
21
+ # the template available on disk.
22
+ #
23
+ # @example Using a git repository as a template
24
+ # PDK::Module::TemplateDir::Base.new('https://github.com/puppetlabs/pdk-templates') do |t|
25
+ # t.render do |filename, content|
26
+ # File.open(filename, 'w') do |file|
27
+ # file.write(content)
28
+ # end
29
+ # end
30
+ # end
31
+ #
32
+ # @raise [ArgumentError] If no block is given to this method.
33
+ # @raise [PDK::CLI::FatalError] (see #clone_repo)
34
+ # @raise [ArgumentError] (see #validate_module_template!)
35
+ #
36
+ # @api public
37
+ def initialize(uri, module_metadata = {}, init = false)
38
+ unless block_given?
39
+ raise ArgumentError, _('%{class_name} must be initialized with a block.') % { class_name: self.class.name }
40
+ end
41
+ unless uri.is_a? PDK::Util::TemplateURI
42
+ raise ArgumentError, _('%{class_name} must be initialized with a PDK::Util::TemplateURI, got a %{uri_type}') % { uri_type: uri.class, class_name: self.class.name }
43
+ end
44
+
45
+ @path, @is_temporary_path = template_path(uri)
46
+ @uri = uri
47
+
48
+ @init = init
49
+ @moduleroot_dir = PDK::Module::TemplateDir.moduleroot_dir(@path)
50
+ @moduleroot_init = PDK::Module::TemplateDir.moduleroot_init(@path)
51
+ @dirs = [@moduleroot_dir]
52
+ @dirs << @moduleroot_init if @init
53
+ @object_dir = File.join(@path, 'object_templates')
54
+
55
+ PDK::Module::TemplateDir.validate_module_template!(@path)
56
+
57
+ @module_metadata = module_metadata
58
+
59
+ template_type = uri.default? ? 'default' : 'custom'
60
+ PDK.analytics.event('TemplateDir', 'initialize', label: template_type)
61
+
62
+ yield self
63
+ ensure
64
+ # If the the path is temporary, clean it up
65
+ if @is_temporary_path
66
+ PDK::Util::Filesystem.rm_rf(@path)
67
+ end
68
+ end
69
+
70
+ # Retrieve identifying metadata for the template.
71
+ #
72
+ # For git repositories, this will return the URL to the repository and
73
+ # a reference to the HEAD.
74
+ #
75
+ # For plain fileystem directories, this will return the URL to the repository only.
76
+ #
77
+ # @return [Hash{String => String}] A hash of identifying metadata.
78
+ #
79
+ # @api public
80
+ # @abstract
81
+ def metadata
82
+ {
83
+ 'pdk-version' => PDK::Util::Version.version_string,
84
+ 'template-url' => nil,
85
+ 'template-ref' => nil,
86
+ }
87
+ end
88
+
89
+ # Loop through the files in the template, yielding each rendered file to
90
+ # the supplied block.
91
+ #
92
+ # @yieldparam dest_path [String] The path of the destination file,
93
+ # relative to the root of the module.
94
+ # @yieldparam dest_content [String] The rendered content of the
95
+ # destination file.
96
+ #
97
+ # @raise [PDK::CLI::FatalError] If the template fails to render.
98
+ #
99
+ # @return [void]
100
+ #
101
+ # @api public
102
+ def render
103
+ require 'pdk/template_file'
104
+
105
+ PDK::Module::TemplateDir.files_in_template(@dirs).each do |template_file, template_loc|
106
+ template_file = template_file.to_s
107
+ PDK.logger.debug(_("Rendering '%{template}'...") % { template: template_file })
108
+ dest_path = template_file.sub(%r{\.erb\Z}, '')
109
+ config = config_for(dest_path)
110
+
111
+ dest_status = if template_loc.start_with?(@moduleroot_init)
112
+ :init
113
+ else
114
+ :manage
115
+ end
116
+
117
+ if config['unmanaged']
118
+ dest_status = :unmanage
119
+ elsif config['delete']
120
+ dest_status = :delete
121
+ else
122
+ begin
123
+ dest_content = PDK::TemplateFile.new(File.join(template_loc, template_file), configs: config, template_dir: self).render
124
+ rescue => error
125
+ error_msg = _(
126
+ "Failed to render template '%{template}'\n" \
127
+ '%{exception}: %{message}',
128
+ ) % { template: template_file, exception: error.class, message: error.message }
129
+ raise PDK::CLI::FatalError, error_msg
130
+ end
131
+ end
132
+
133
+ yield dest_path, dest_content, dest_status
134
+ end
135
+ end
136
+
137
+ # Searches the template directory for template files that can be used to
138
+ # render files for the specified object type.
139
+ #
140
+ # @param object_type [Symbol] The object type, e.g. (`:class`,
141
+ # `:defined_type`, `:fact`, etc).
142
+ #
143
+ # @return [Hash{Symbol => String}] if the templates are available in the
144
+ # template dir, otherwise `nil`. The returned hash can contain two keys,
145
+ # :object contains the path on disk to the template for the object, :spec
146
+ # contains the path on disk to the template for the object's spec file
147
+ # (if available).
148
+ #
149
+ # @api public
150
+ def object_template_for(object_type)
151
+ object_path = File.join(@object_dir, "#{object_type}.erb")
152
+ type_path = File.join(@object_dir, "#{object_type}_type.erb")
153
+ device_path = File.join(@object_dir, "#{object_type}_device.erb")
154
+ spec_path = File.join(@object_dir, "#{object_type}_spec.erb")
155
+ type_spec_path = File.join(@object_dir, "#{object_type}_type_spec.erb")
156
+
157
+ if PDK::Util::Filesystem.file?(object_path) && PDK::Util::Filesystem.readable?(object_path)
158
+ result = { object: object_path }
159
+ result[:type] = type_path if PDK::Util::Filesystem.file?(type_path) && PDK::Util::Filesystem.readable?(type_path)
160
+ result[:spec] = spec_path if PDK::Util::Filesystem.file?(spec_path) && PDK::Util::Filesystem.readable?(spec_path)
161
+ result[:device] = device_path if PDK::Util::Filesystem.file?(device_path) && PDK::Util::Filesystem.readable?(device_path)
162
+ result[:type_spec] = type_spec_path if PDK::Util::Filesystem.file?(type_spec_path) && PDK::Util::Filesystem.readable?(type_spec_path)
163
+ result
164
+ else
165
+ nil
166
+ end
167
+ end
168
+
169
+ # Generate a hash of data to be used when rendering object templates.
170
+ #
171
+ # Read `config_defaults.yml` from the root of the template directory (if
172
+ # it exists) build a hash of values from the value of the `:global`
173
+ # key.
174
+ #
175
+ # @return [Hash] The data that will be available to the template via the
176
+ # `@configs` instance variable.
177
+ #
178
+ # @api private
179
+ def object_config
180
+ config_for(nil)
181
+ end
182
+
183
+ # Generate a hash of data to be used when rendering the specified
184
+ # template.
185
+ #
186
+ # @param dest_path [String] The destination path of the file that the
187
+ # data is for, relative to the root of the module.
188
+ #
189
+ # @return [Hash] The data that will be available to the template via the
190
+ # `@configs` instance variable.
191
+ #
192
+ # @api private
193
+ def config_for(dest_path, sync_config_path = nil)
194
+ require 'pdk/util'
195
+ require 'pdk/analytics'
196
+
197
+ module_root = PDK::Util.module_root
198
+ sync_config_path ||= File.join(module_root, '.sync.yml') unless module_root.nil?
199
+ config_path = File.join(@path, 'config_defaults.yml')
200
+
201
+ if @config.nil?
202
+ require 'deep_merge'
203
+ conf_defaults = read_config(config_path)
204
+ @sync_config = read_config(sync_config_path) unless sync_config_path.nil?
205
+ @config = conf_defaults
206
+ @config.deep_merge!(@sync_config, knockout_prefix: '---') unless @sync_config.nil?
207
+ end
208
+ file_config = @config.fetch(:global, {})
209
+ file_config['module_metadata'] = @module_metadata
210
+ file_config.merge!(@config.fetch(dest_path, {})) unless dest_path.nil?
211
+ file_config.merge!(@config).tap do |c|
212
+ if uri.default?
213
+ file_value = if c['unmanaged']
214
+ 'unmanaged'
215
+ elsif c['delete']
216
+ 'deleted'
217
+ elsif @sync_config && @sync_config.key?(dest_path)
218
+ 'customized'
219
+ else
220
+ 'default'
221
+ end
222
+
223
+ PDK.analytics.event('TemplateDir', 'file', label: dest_path, value: file_value)
224
+ end
225
+ end
226
+ end
227
+
228
+ # Generates a hash of data from a given yaml file location.
229
+ #
230
+ # @param loc [String] The path of the yaml config file.
231
+ #
232
+ # @warn If the specified path is not a valid yaml file. Returns an empty Hash
233
+ # if so.
234
+ #
235
+ # @return [Hash] The data that has been read in from the given yaml file.
236
+ #
237
+ # @api private
238
+ def read_config(loc)
239
+ if PDK::Util::Filesystem.file?(loc) && PDK::Util::Filesystem.readable?(loc)
240
+ require 'yaml'
241
+
242
+ begin
243
+ YAML.safe_load(PDK::Util::Filesystem.read_file(loc), [], [], true)
244
+ rescue Psych::SyntaxError => e
245
+ PDK.logger.warn _("'%{file}' is not a valid YAML file: %{problem} %{context} at line %{line} column %{column}") % {
246
+ file: loc,
247
+ problem: e.problem,
248
+ context: e.context,
249
+ line: e.line,
250
+ column: e.column,
251
+ }
252
+ {}
253
+ end
254
+ else
255
+ {}
256
+ end
257
+ end
258
+
259
+ # @return [Path, Boolean] The path to the Template and whether this path is temporary. Temporary paths
260
+ # are deleted once the object has yielded
261
+ # @api private
262
+ def template_path(uri)
263
+ [uri.shell_path, false]
264
+ end
265
+ end
266
+ end
267
+ end
268
+ end
@@ -0,0 +1,91 @@
1
+ require 'pdk'
2
+ require 'pdk/module/template_dir/base'
3
+
4
+ module PDK
5
+ module Module
6
+ module TemplateDir
7
+ class Git < Base
8
+ def template_path(uri)
9
+ # We don't do a checkout of local-path repos. There are lots of edge
10
+ # cases or user un-expectations.
11
+ if PDK::Util::Git.work_tree?(uri.shell_path)
12
+ PDK.logger.warn _("Repository '%{repo}' has a work-tree; skipping git reset.") % {
13
+ repo: uri.shell_path,
14
+ }
15
+ [uri.shell_path, false]
16
+ else
17
+ # This is either a bare local repo or a remote. either way it needs cloning.
18
+ # A "remote" can also be git repo on the local filsystem.
19
+ [clone_template_repo(uri), true]
20
+ end
21
+ end
22
+
23
+ # For git repositories, this will return the URL to the repository and
24
+ # a reference to the HEAD.
25
+ #
26
+ # @return [Hash{String => String}] A hash of identifying metadata.
27
+ def metadata
28
+ super.merge('template-url' => uri.metadata_format, 'template-ref' => cache_template_ref(@path))
29
+ end
30
+
31
+ private
32
+
33
+ def cache_template_ref(path, ref = nil)
34
+ require 'pdk/util/git'
35
+
36
+ @template_ref ||= PDK::Util::Git.describe(File.join(path, '.git'), ref)
37
+ end
38
+
39
+ # @return [String] Path to working directory into which template repo has been cloned and reset
40
+ #
41
+ # @raise [PDK::CLI::FatalError] If unable to clone the given origin_repo into a tempdir.
42
+ # @raise [PDK::CLI::FatalError] If reset HEAD of the cloned repo to desired ref.
43
+ #
44
+ # @api private
45
+ def clone_template_repo(uri)
46
+ # @todo When switching this over to using rugged, cache the cloned
47
+ # template repo in `%AppData%` or `$XDG_CACHE_DIR` and update before
48
+ # use.
49
+ require 'pdk/util'
50
+ require 'pdk/util/git'
51
+
52
+ temp_dir = PDK::Util.make_tmpdir_name('pdk-templates')
53
+ origin_repo = uri.bare_uri
54
+ git_ref = uri.uri_fragment
55
+
56
+ clone_result = PDK::Util::Git.git('clone', origin_repo, temp_dir)
57
+
58
+ if clone_result[:exit_code].zero?
59
+ checkout_template_ref(temp_dir, git_ref)
60
+ else
61
+ PDK.logger.error clone_result[:stdout]
62
+ PDK.logger.error clone_result[:stderr]
63
+ raise PDK::CLI::FatalError, _("Unable to clone git repository at '%{repo}' into '%{dest}'.") % { repo: origin_repo, dest: temp_dir }
64
+ end
65
+
66
+ PDK::Util.canonical_path(temp_dir)
67
+ end
68
+
69
+ # @api private
70
+ def checkout_template_ref(path, ref)
71
+ require 'pdk/util/git'
72
+
73
+ if PDK::Util::Git.work_dir_clean?(path)
74
+ Dir.chdir(path) do
75
+ full_ref = PDK::Util::Git.ls_remote(path, ref)
76
+ cache_template_ref(path, full_ref)
77
+ reset_result = PDK::Util::Git.git('reset', '--hard', full_ref)
78
+ return if reset_result[:exit_code].zero?
79
+
80
+ PDK.logger.error reset_result[:stdout]
81
+ PDK.logger.error reset_result[:stderr]
82
+ raise PDK::CLI::FatalError, _("Unable to checkout '%{ref}' of git repository at '%{path}'.") % { ref: ref, path: path }
83
+ end
84
+ else
85
+ PDK.logger.warn _("Uncommitted changes found when attempting to checkout '%{ref}' of git repository at '%{path}'; skipping git reset.") % { ref: ref, path: path }
86
+ end
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end