pdk 1.14.1 → 1.15.0

Sign up to get free protection for your applications and to get access to all the features.
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