pdk-akerl 1.8.0.1
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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +826 -0
- data/LICENSE +201 -0
- data/README.md +133 -0
- data/exe/pdk +10 -0
- data/lib/pdk.rb +10 -0
- data/lib/pdk/answer_file.rb +121 -0
- data/lib/pdk/cli.rb +113 -0
- data/lib/pdk/cli/build.rb +76 -0
- data/lib/pdk/cli/bundle.rb +42 -0
- data/lib/pdk/cli/convert.rb +41 -0
- data/lib/pdk/cli/errors.rb +23 -0
- data/lib/pdk/cli/exec.rb +246 -0
- data/lib/pdk/cli/exec_group.rb +67 -0
- data/lib/pdk/cli/module.rb +14 -0
- data/lib/pdk/cli/module/build.rb +14 -0
- data/lib/pdk/cli/module/generate.rb +45 -0
- data/lib/pdk/cli/new.rb +17 -0
- data/lib/pdk/cli/new/class.rb +32 -0
- data/lib/pdk/cli/new/defined_type.rb +30 -0
- data/lib/pdk/cli/new/module.rb +41 -0
- data/lib/pdk/cli/new/provider.rb +27 -0
- data/lib/pdk/cli/new/task.rb +31 -0
- data/lib/pdk/cli/test.rb +12 -0
- data/lib/pdk/cli/test/unit.rb +88 -0
- data/lib/pdk/cli/update.rb +32 -0
- data/lib/pdk/cli/util.rb +193 -0
- data/lib/pdk/cli/util/command_redirector.rb +26 -0
- data/lib/pdk/cli/util/interview.rb +63 -0
- data/lib/pdk/cli/util/option_normalizer.rb +53 -0
- data/lib/pdk/cli/util/option_validator.rb +56 -0
- data/lib/pdk/cli/validate.rb +124 -0
- data/lib/pdk/generate.rb +11 -0
- data/lib/pdk/generate/defined_type.rb +49 -0
- data/lib/pdk/generate/module.rb +318 -0
- data/lib/pdk/generate/provider.rb +82 -0
- data/lib/pdk/generate/puppet_class.rb +48 -0
- data/lib/pdk/generate/puppet_object.rb +288 -0
- data/lib/pdk/generate/task.rb +86 -0
- data/lib/pdk/i18n.rb +4 -0
- data/lib/pdk/logger.rb +28 -0
- data/lib/pdk/module.rb +21 -0
- data/lib/pdk/module/build.rb +214 -0
- data/lib/pdk/module/convert.rb +209 -0
- data/lib/pdk/module/metadata.rb +193 -0
- data/lib/pdk/module/templatedir.rb +313 -0
- data/lib/pdk/module/update.rb +111 -0
- data/lib/pdk/module/update_manager.rb +210 -0
- data/lib/pdk/report.rb +112 -0
- data/lib/pdk/report/event.rb +357 -0
- data/lib/pdk/template_file.rb +89 -0
- data/lib/pdk/tests/unit.rb +213 -0
- data/lib/pdk/util.rb +271 -0
- data/lib/pdk/util/bundler.rb +253 -0
- data/lib/pdk/util/filesystem.rb +12 -0
- data/lib/pdk/util/git.rb +74 -0
- data/lib/pdk/util/puppet_version.rb +242 -0
- data/lib/pdk/util/ruby_version.rb +147 -0
- data/lib/pdk/util/vendored_file.rb +88 -0
- data/lib/pdk/util/version.rb +42 -0
- data/lib/pdk/util/windows.rb +13 -0
- data/lib/pdk/util/windows/api_types.rb +57 -0
- data/lib/pdk/util/windows/file.rb +36 -0
- data/lib/pdk/util/windows/string.rb +16 -0
- data/lib/pdk/validate.rb +14 -0
- data/lib/pdk/validate/base_validator.rb +209 -0
- data/lib/pdk/validate/metadata/metadata_json_lint.rb +86 -0
- data/lib/pdk/validate/metadata/metadata_syntax.rb +109 -0
- data/lib/pdk/validate/metadata_validator.rb +30 -0
- data/lib/pdk/validate/puppet/puppet_lint.rb +67 -0
- data/lib/pdk/validate/puppet/puppet_syntax.rb +112 -0
- data/lib/pdk/validate/puppet_validator.rb +30 -0
- data/lib/pdk/validate/ruby/rubocop.rb +77 -0
- data/lib/pdk/validate/ruby_validator.rb +29 -0
- data/lib/pdk/validate/tasks/metadata_lint.rb +126 -0
- data/lib/pdk/validate/tasks/name.rb +88 -0
- data/lib/pdk/validate/tasks_validator.rb +33 -0
- data/lib/pdk/version.rb +4 -0
- data/locales/config.yaml +21 -0
- data/locales/pdk.pot +1283 -0
- metadata +304 -0
@@ -0,0 +1,313 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
require 'deep_merge'
|
3
|
+
require 'pdk/util'
|
4
|
+
require 'pdk/util/git'
|
5
|
+
require 'pdk/cli/errors'
|
6
|
+
require 'pdk/template_file'
|
7
|
+
|
8
|
+
module PDK
|
9
|
+
module Module
|
10
|
+
class TemplateDir
|
11
|
+
attr_accessor :module_metadata
|
12
|
+
|
13
|
+
# Initialises the TemplateDir object with the path or URL to the template
|
14
|
+
# and the block of code to run to be run while the template is available.
|
15
|
+
#
|
16
|
+
# The template directory is only guaranteed to be available on disk
|
17
|
+
# within the scope of the block passed to this method.
|
18
|
+
#
|
19
|
+
# @param path_or_url [String] The path to a directory to use as the
|
20
|
+
# template or a URL to a git repository.
|
21
|
+
# @param module_metadata [Hash] A Hash containing the module metadata.
|
22
|
+
# Defaults to an empty Hash.
|
23
|
+
# @yieldparam self [PDK::Module::TemplateDir] The initialised object with
|
24
|
+
# the template available on disk.
|
25
|
+
#
|
26
|
+
# @example Using a git repository as a template
|
27
|
+
# PDK::Module::TemplateDir.new('https://github.com/puppetlabs/pdk-templates') do |t|
|
28
|
+
# t.render do |filename, content|
|
29
|
+
# File.open(filename, 'w') do |file|
|
30
|
+
# file.write(content)
|
31
|
+
# end
|
32
|
+
# end
|
33
|
+
# end
|
34
|
+
#
|
35
|
+
# @raise [ArgumentError] If no block is given to this method.
|
36
|
+
# @raise [PDK::CLI::FatalError] (see #clone_repo)
|
37
|
+
# @raise [ArgumentError] (see #validate_module_template!)
|
38
|
+
#
|
39
|
+
# @api public
|
40
|
+
def initialize(path_or_url, module_metadata = {}, init = false)
|
41
|
+
unless block_given?
|
42
|
+
raise ArgumentError, _('%{class_name} must be initialized with a block.') % { class_name: self.class.name }
|
43
|
+
end
|
44
|
+
|
45
|
+
if PDK::Util::Git.repo?(path_or_url)
|
46
|
+
@path = self.class.clone_template_repo(path_or_url)
|
47
|
+
@repo = path_or_url
|
48
|
+
else
|
49
|
+
@path = path_or_url
|
50
|
+
end
|
51
|
+
|
52
|
+
@init = init
|
53
|
+
@moduleroot_dir = File.join(@path, 'moduleroot')
|
54
|
+
@moduleroot_init = File.join(@path, 'moduleroot_init')
|
55
|
+
@dirs = [@moduleroot_dir]
|
56
|
+
@dirs << @moduleroot_init if @init
|
57
|
+
@object_dir = File.join(@path, 'object_templates')
|
58
|
+
|
59
|
+
validate_module_template!
|
60
|
+
|
61
|
+
@module_metadata = module_metadata
|
62
|
+
|
63
|
+
yield self
|
64
|
+
ensure
|
65
|
+
# If we cloned a git repo to get the template, remove the clone once
|
66
|
+
# we're done with it.
|
67
|
+
if @repo
|
68
|
+
FileUtils.remove_dir(@path)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Retrieve identifying metadata for the template.
|
73
|
+
#
|
74
|
+
# For git repositories, this will return the URL to the repository and
|
75
|
+
# a reference to the HEAD.
|
76
|
+
#
|
77
|
+
# @return [Hash{String => String}] A hash of identifying metadata.
|
78
|
+
#
|
79
|
+
# @api public
|
80
|
+
def metadata
|
81
|
+
result = {
|
82
|
+
'pdk-version' => PDK::Util::Version.version_string,
|
83
|
+
}
|
84
|
+
|
85
|
+
result['template-url'] = @repo ? @repo : @path
|
86
|
+
|
87
|
+
ref_result = PDK::Util::Git.git('--git-dir', File.join(@path, '.git'), 'describe', '--all', '--long', '--always')
|
88
|
+
result['template-ref'] = ref_result[:stdout].strip if ref_result[:exit_code].zero?
|
89
|
+
|
90
|
+
result
|
91
|
+
end
|
92
|
+
|
93
|
+
# Loop through the files in the template, yielding each rendered file to
|
94
|
+
# the supplied block.
|
95
|
+
#
|
96
|
+
# @yieldparam dest_path [String] The path of the destination file,
|
97
|
+
# relative to the root of the module.
|
98
|
+
# @yieldparam dest_content [String] The rendered content of the
|
99
|
+
# destination file.
|
100
|
+
#
|
101
|
+
# @raise [PDK::CLI::FatalError] If the template fails to render.
|
102
|
+
#
|
103
|
+
# @return [void]
|
104
|
+
#
|
105
|
+
# @api public
|
106
|
+
def render
|
107
|
+
PDK::Module::TemplateDir.files_in_template(@dirs).each do |template_file, template_loc|
|
108
|
+
template_file = template_file.to_s
|
109
|
+
PDK.logger.debug(_("Rendering '%{template}'...") % { template: template_file })
|
110
|
+
dest_path = template_file.sub(%r{\.erb\Z}, '')
|
111
|
+
config = config_for(dest_path)
|
112
|
+
dest_status = :manage
|
113
|
+
|
114
|
+
if config['unmanaged']
|
115
|
+
dest_status = :unmanage
|
116
|
+
elsif config['delete']
|
117
|
+
dest_status = :delete
|
118
|
+
else
|
119
|
+
begin
|
120
|
+
dest_content = PDK::TemplateFile.new(File.join(template_loc, template_file), configs: config).render
|
121
|
+
rescue => e
|
122
|
+
error_msg = _(
|
123
|
+
"Failed to render template '%{template}'\n" \
|
124
|
+
'%{exception}: %{message}',
|
125
|
+
) % { template: template_file, exception: e.class, message: e.message }
|
126
|
+
raise PDK::CLI::FatalError, error_msg
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
yield dest_path, dest_content, dest_status
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# Searches the template directory for template files that can be used to
|
135
|
+
# render files for the specified object type.
|
136
|
+
#
|
137
|
+
# @param object_type [Symbol] The object type, e.g. (`:class`,
|
138
|
+
# `:defined_type`, `:fact`, etc).
|
139
|
+
#
|
140
|
+
# @return [Hash{Symbol => String}] if the templates are available in the
|
141
|
+
# template dir, otherwise `nil`. The returned hash can contain two keys,
|
142
|
+
# :object contains the path on disk to the template for the object, :spec
|
143
|
+
# contains the path on disk to the template for the object's spec file
|
144
|
+
# (if available).
|
145
|
+
#
|
146
|
+
# @api public
|
147
|
+
def object_template_for(object_type)
|
148
|
+
object_path = File.join(@object_dir, "#{object_type}.erb")
|
149
|
+
type_path = File.join(@object_dir, "#{object_type}_type.erb")
|
150
|
+
spec_path = File.join(@object_dir, "#{object_type}_spec.erb")
|
151
|
+
type_spec_path = File.join(@object_dir, "#{object_type}_type_spec.erb")
|
152
|
+
|
153
|
+
if File.file?(object_path) && File.readable?(object_path)
|
154
|
+
result = { object: object_path }
|
155
|
+
result[:type] = type_path if File.file?(type_path) && File.readable?(type_path)
|
156
|
+
result[:spec] = spec_path if File.file?(spec_path) && File.readable?(spec_path)
|
157
|
+
result[:type_spec] = type_spec_path if File.file?(type_spec_path) && File.readable?(type_spec_path)
|
158
|
+
result
|
159
|
+
else
|
160
|
+
nil
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
# Generate a hash of data to be used when rendering object templates.
|
165
|
+
#
|
166
|
+
# Read `config_defaults.yml` from the root of the template directory (if
|
167
|
+
# it exists) build a hash of values from the value of the `:global`
|
168
|
+
# key.
|
169
|
+
#
|
170
|
+
# @return [Hash] The data that will be available to the template via the
|
171
|
+
# `@configs` instance variable.
|
172
|
+
#
|
173
|
+
# @api private
|
174
|
+
def object_config
|
175
|
+
config_for(nil)
|
176
|
+
end
|
177
|
+
|
178
|
+
# Validate the content of the template directory.
|
179
|
+
#
|
180
|
+
# @raise [ArgumentError] If the specified path is not a directory.
|
181
|
+
# @raise [ArgumentError] If the template directory does not contain
|
182
|
+
# a directory called 'moduleroot'.
|
183
|
+
#
|
184
|
+
# @return [void]
|
185
|
+
#
|
186
|
+
# @api private
|
187
|
+
def validate_module_template!
|
188
|
+
# rubocop:disable Style/GuardClause
|
189
|
+
unless File.directory?(@path)
|
190
|
+
if PDK::Util.package_install? && File.fnmatch?(File.join(PDK::Util.package_cachedir, '*'), @path)
|
191
|
+
raise ArgumentError, _('The built-in template has substantially changed. Please run "pdk convert" on your module to continue.')
|
192
|
+
else
|
193
|
+
raise ArgumentError, _("The specified template '%{path}' is not a directory.") % { path: @path }
|
194
|
+
end
|
195
|
+
end
|
196
|
+
|
197
|
+
unless File.directory?(@moduleroot_dir)
|
198
|
+
raise ArgumentError, _("The template at '%{path}' does not contain a 'moduleroot/' directory.") % { path: @path }
|
199
|
+
end
|
200
|
+
|
201
|
+
unless File.directory?(@moduleroot_init)
|
202
|
+
# rubocop:disable Metrics/LineLength
|
203
|
+
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 }
|
204
|
+
# rubocop:enable Metrics/LineLength Style/GuardClause
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
# Get a list of template files in the template directory.
|
209
|
+
#
|
210
|
+
# @return [Hash{String=>String}] A hash of key file names and
|
211
|
+
# value locations.
|
212
|
+
#
|
213
|
+
# @api public
|
214
|
+
def self.files_in_template(dirs)
|
215
|
+
temp_paths = []
|
216
|
+
dirlocs = []
|
217
|
+
dirs.each do |dir|
|
218
|
+
raise ArgumentError, _("The directory '%{dir}' doesn't exist") % { dir: dir } unless Dir.exist?(dir)
|
219
|
+
temp_paths += Dir.glob(File.join(dir, '**', '*'), File::FNM_DOTMATCH).select do |template_path|
|
220
|
+
if File.file?(template_path) && !File.symlink?(template_path)
|
221
|
+
dirlocs << dir
|
222
|
+
end
|
223
|
+
end
|
224
|
+
temp_paths.map do |template_path|
|
225
|
+
template_path.sub!(%r{\A#{Regexp.escape(dir)}#{Regexp.escape(File::SEPARATOR)}}, '')
|
226
|
+
end
|
227
|
+
end
|
228
|
+
Hash[temp_paths.zip dirlocs]
|
229
|
+
end
|
230
|
+
|
231
|
+
# Generate a hash of data to be used when rendering the specified
|
232
|
+
# template.
|
233
|
+
#
|
234
|
+
# @param dest_path [String] The destination path of the file that the
|
235
|
+
# data is for, relative to the root of the module.
|
236
|
+
#
|
237
|
+
# @return [Hash] The data that will be available to the template via the
|
238
|
+
# `@configs` instance variable.
|
239
|
+
#
|
240
|
+
# @api private
|
241
|
+
def config_for(dest_path, sync_config_path = nil)
|
242
|
+
module_root = PDK::Util.module_root
|
243
|
+
sync_config_path ||= File.join(module_root, '.sync.yml') unless module_root.nil?
|
244
|
+
config_path = File.join(@path, 'config_defaults.yml')
|
245
|
+
|
246
|
+
if @config.nil?
|
247
|
+
conf_defaults = read_config(config_path)
|
248
|
+
sync_config = read_config(sync_config_path) unless sync_config_path.nil?
|
249
|
+
@config = conf_defaults
|
250
|
+
@config.deep_merge!(sync_config, knockout_prefix: '---') unless sync_config.nil?
|
251
|
+
end
|
252
|
+
file_config = @config.fetch(:global, {})
|
253
|
+
file_config['module_metadata'] = @module_metadata
|
254
|
+
file_config.merge!(@config.fetch(dest_path, {})) unless dest_path.nil?
|
255
|
+
file_config.merge!(@config)
|
256
|
+
end
|
257
|
+
|
258
|
+
# Generates a hash of data from a given yaml file location.
|
259
|
+
#
|
260
|
+
# @param loc [String] The path of the yaml config file.
|
261
|
+
#
|
262
|
+
# @warn If the specified path is not a valid yaml file. Returns an empty Hash
|
263
|
+
# if so.
|
264
|
+
#
|
265
|
+
# @return [Hash] The data that has been read in from the given yaml file.
|
266
|
+
#
|
267
|
+
# @api private
|
268
|
+
def read_config(loc)
|
269
|
+
if File.file?(loc) && File.readable?(loc)
|
270
|
+
begin
|
271
|
+
YAML.safe_load(File.read(loc), [], [], true)
|
272
|
+
rescue StandardError => e
|
273
|
+
PDK.logger.warn(_("'%{file}' is not a valid YAML file: %{message}") % { file: loc, message: e.message })
|
274
|
+
{}
|
275
|
+
end
|
276
|
+
else
|
277
|
+
{}
|
278
|
+
end
|
279
|
+
end
|
280
|
+
|
281
|
+
# @return [String] Path to working directory into which template repo has been cloned and reset
|
282
|
+
#
|
283
|
+
# @raise [PDK::CLI::FatalError] If unable to clone the given origin_repo into a tempdir.
|
284
|
+
# @raise [PDK::CLI::FatalError] If reset HEAD of the cloned repo to desired ref.
|
285
|
+
#
|
286
|
+
# @api private
|
287
|
+
def self.clone_template_repo(origin_repo)
|
288
|
+
# @todo When switching this over to using rugged, cache the cloned
|
289
|
+
# template repo in `%AppData%` or `$XDG_CACHE_DIR` and update before
|
290
|
+
# use.
|
291
|
+
temp_dir = PDK::Util.make_tmpdir_name('pdk-templates')
|
292
|
+
git_ref = (origin_repo == PDK::Util.default_template_url) ? PDK::Util.default_template_ref : 'origin/master'
|
293
|
+
|
294
|
+
clone_result = PDK::Util::Git.git('clone', origin_repo, temp_dir)
|
295
|
+
|
296
|
+
if clone_result[:exit_code].zero?
|
297
|
+
reset_result = PDK::Util::Git.git('-C', temp_dir, 'reset', '--hard', git_ref)
|
298
|
+
unless reset_result[:exit_code].zero?
|
299
|
+
PDK.logger.error reset_result[:stdout]
|
300
|
+
PDK.logger.error reset_result[:stderr]
|
301
|
+
raise PDK::CLI::FatalError, _("Unable to set HEAD of git repository at '%{repo}' to ref:'%{ref}'.") % { repo: temp_dir, ref: git_ref }
|
302
|
+
end
|
303
|
+
else
|
304
|
+
PDK.logger.error clone_result[:stdout]
|
305
|
+
PDK.logger.error clone_result[:stderr]
|
306
|
+
raise PDK::CLI::FatalError, _("Unable to clone git repository at '%{repo}' into '%{dest}'.") % { repo: origin_repo, dest: temp_dir }
|
307
|
+
end
|
308
|
+
|
309
|
+
PDK::Util.canonical_path(temp_dir)
|
310
|
+
end
|
311
|
+
end
|
312
|
+
end
|
313
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
require 'pdk/module/convert'
|
2
|
+
|
3
|
+
module PDK
|
4
|
+
module Module
|
5
|
+
class Update < Convert
|
6
|
+
GIT_DESCRIBE_PATTERN = %r{\A(?<base>.+?)-(?<additional_commits>\d+)-g(?<sha>.+)\Z}
|
7
|
+
|
8
|
+
def run
|
9
|
+
stage_changes!
|
10
|
+
|
11
|
+
if current_version == new_version
|
12
|
+
PDK.logger.debug _('This module is already up to date with version %{version} of the template.') % {
|
13
|
+
version: new_version,
|
14
|
+
}
|
15
|
+
end
|
16
|
+
|
17
|
+
unless update_manager.changes?
|
18
|
+
PDK::Report.default_target.puts(_('No changes required.'))
|
19
|
+
return
|
20
|
+
end
|
21
|
+
|
22
|
+
PDK.logger.info(update_message)
|
23
|
+
|
24
|
+
print_summary
|
25
|
+
full_report('update_report.txt') unless update_manager.changes[:modified].empty?
|
26
|
+
|
27
|
+
return if noop?
|
28
|
+
|
29
|
+
unless force?
|
30
|
+
message = _('Do you want to continue and make these changes to your module?')
|
31
|
+
return unless PDK::CLI::Util.prompt_for_yes(message)
|
32
|
+
end
|
33
|
+
|
34
|
+
# Remove these files straight away as these changes are not something that the user needs to review.
|
35
|
+
if needs_bundle_update?
|
36
|
+
update_manager.unlink_file('Gemfile.lock')
|
37
|
+
update_manager.unlink_file(File.join('.bundle', 'config'))
|
38
|
+
end
|
39
|
+
|
40
|
+
update_manager.sync_changes!
|
41
|
+
|
42
|
+
PDK::Util::Bundler.ensure_bundle! if needs_bundle_update?
|
43
|
+
|
44
|
+
print_result 'Update completed'
|
45
|
+
end
|
46
|
+
|
47
|
+
def module_metadata
|
48
|
+
@module_metadata ||= PDK::Module::Metadata.from_file('metadata.json')
|
49
|
+
rescue ArgumentError => e
|
50
|
+
raise PDK::CLI::ExitWithError, e.message
|
51
|
+
end
|
52
|
+
|
53
|
+
def template_url
|
54
|
+
@template_url ||= module_metadata.data['template-url']
|
55
|
+
end
|
56
|
+
|
57
|
+
def current_version
|
58
|
+
@current_version ||= describe_ref_to_s(current_template_version)
|
59
|
+
end
|
60
|
+
|
61
|
+
def new_version
|
62
|
+
@new_version ||= fetch_remote_version(new_template_version)
|
63
|
+
end
|
64
|
+
|
65
|
+
private
|
66
|
+
|
67
|
+
def current_template_version
|
68
|
+
@current_template_version ||= module_metadata.data['template-ref']
|
69
|
+
end
|
70
|
+
|
71
|
+
def describe_ref_to_s(describe_ref)
|
72
|
+
data = GIT_DESCRIBE_PATTERN.match(describe_ref)
|
73
|
+
|
74
|
+
return data if data.nil?
|
75
|
+
|
76
|
+
if data[:base].start_with?('heads/')
|
77
|
+
"#{data[:base].gsub(%r{^heads/}, '')}@#{data[:sha]}"
|
78
|
+
else
|
79
|
+
data[:base]
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def new_template_version
|
84
|
+
PDK::Util.default_template_ref
|
85
|
+
end
|
86
|
+
|
87
|
+
def fetch_remote_version(version)
|
88
|
+
return version unless version.include?('/')
|
89
|
+
|
90
|
+
branch = version.partition('/').last
|
91
|
+
sha_length = GIT_DESCRIBE_PATTERN.match(current_template_version)[:sha].length - 1
|
92
|
+
"#{branch}@#{PDK::Util::Git.ls_remote(template_url, "refs/heads/#{branch}")[0..sha_length]}"
|
93
|
+
end
|
94
|
+
|
95
|
+
def update_message
|
96
|
+
format_string = if template_url == PDK::Util.puppetlabs_template_url
|
97
|
+
_('Updating %{module_name} using the default template, from %{current_version} to %{new_version}')
|
98
|
+
else
|
99
|
+
_('Updating %{module_name} using the template at %{template_url}, from %{current_version} to %{new_version}')
|
100
|
+
end
|
101
|
+
|
102
|
+
format_string % {
|
103
|
+
module_name: module_metadata.data['name'],
|
104
|
+
template_url: template_url,
|
105
|
+
current_version: current_version,
|
106
|
+
new_version: new_version,
|
107
|
+
}
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
@@ -0,0 +1,210 @@
|
|
1
|
+
require 'diff/lcs'
|
2
|
+
require 'diff/lcs/hunk'
|
3
|
+
require 'English'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'set'
|
6
|
+
require 'pdk/util/filesystem'
|
7
|
+
|
8
|
+
module PDK
|
9
|
+
module Module
|
10
|
+
class UpdateManager
|
11
|
+
# Initialises a blank UpdateManager object, which is used to store and
|
12
|
+
# process file additions/removals/modifications.
|
13
|
+
def initialize
|
14
|
+
@modified_files = Set.new
|
15
|
+
@added_files = Set.new
|
16
|
+
@removed_files = Set.new
|
17
|
+
@diff_cache = {}
|
18
|
+
end
|
19
|
+
|
20
|
+
# Store a pending modification to an existing file.
|
21
|
+
#
|
22
|
+
# @param path [String] The path to the file to be modified.
|
23
|
+
# @param content [String] The new content of the file.
|
24
|
+
def modify_file(path, content)
|
25
|
+
@modified_files << { path: path, content: content }
|
26
|
+
end
|
27
|
+
|
28
|
+
# Store a pending file addition.
|
29
|
+
#
|
30
|
+
# @param path [String] The path where the file will be created.
|
31
|
+
# @param content [String] The content of the new file.
|
32
|
+
def add_file(path, content)
|
33
|
+
@added_files << { path: path, content: content }
|
34
|
+
end
|
35
|
+
|
36
|
+
# Store a pending file removal.
|
37
|
+
#
|
38
|
+
# @param path [String] The path to the file to be removed.
|
39
|
+
def remove_file(path)
|
40
|
+
@removed_files << path
|
41
|
+
end
|
42
|
+
|
43
|
+
# Generate a summary of the changes that will be applied to the module.
|
44
|
+
#
|
45
|
+
# @raise (see #calculate_diffs)
|
46
|
+
# @return [Hash{Symbol => Set,Hash}] the summary of the pending changes.
|
47
|
+
def changes
|
48
|
+
calculate_diffs
|
49
|
+
|
50
|
+
{
|
51
|
+
added: @added_files,
|
52
|
+
removed: @removed_files.select { |f| File.exist?(f) },
|
53
|
+
modified: @diff_cache.reject { |_, value| value.nil? },
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
# Check if there are any pending changes to apply to the module.
|
58
|
+
#
|
59
|
+
# @raise (see #changes)
|
60
|
+
# @return [Boolean] true if there are changes to apply to the module.
|
61
|
+
def changes?
|
62
|
+
!changes[:added].empty? ||
|
63
|
+
!changes[:removed].empty? ||
|
64
|
+
changes[:modified].any? { |_, value| !value.nil? }
|
65
|
+
end
|
66
|
+
|
67
|
+
# Check if the update manager will change the specified file upon sync.
|
68
|
+
#
|
69
|
+
# @param path [String] The path to the file.
|
70
|
+
#
|
71
|
+
# @raise (see #changes)
|
72
|
+
# @return [Boolean] true if the file will be changed.
|
73
|
+
def changed?(path)
|
74
|
+
changes[:added].any? { |add| add[:path] == path } ||
|
75
|
+
changes[:removed].include?(path) ||
|
76
|
+
changes[:modified].keys.include?(path)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Apply any pending changes stored in the UpdateManager to the module.
|
80
|
+
#
|
81
|
+
# @raise (see #calculate_diffs)
|
82
|
+
# @raise (see #write_file)
|
83
|
+
# @raise (see #unlink_file)
|
84
|
+
def sync_changes!
|
85
|
+
calculate_diffs
|
86
|
+
|
87
|
+
files_to_write = @added_files
|
88
|
+
files_to_write += @modified_files.reject { |file| @diff_cache[file[:path]].nil? }
|
89
|
+
|
90
|
+
@removed_files.each do |file|
|
91
|
+
unlink_file(file)
|
92
|
+
end
|
93
|
+
|
94
|
+
files_to_write.each do |file|
|
95
|
+
write_file(file[:path], file[:content])
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
# Remove a file from disk.
|
100
|
+
#
|
101
|
+
# Like FileUtils.rm_f, this method will not fail if the file does not
|
102
|
+
# exist. Unlike FileUtils.rm_f, this method will not blindly swallow all
|
103
|
+
# exceptions.
|
104
|
+
#
|
105
|
+
# @param path [String] The path to the file to be removed.
|
106
|
+
#
|
107
|
+
# @raise [PDK::CLI::ExitWithError] if the file could not be removed.
|
108
|
+
def unlink_file(path)
|
109
|
+
if File.file?(path)
|
110
|
+
PDK.logger.debug(_("unlinking '%{path}'") % { path: path })
|
111
|
+
FileUtils.rm(path)
|
112
|
+
else
|
113
|
+
PDK.logger.debug(_("'%{path}': already gone") % { path: path })
|
114
|
+
end
|
115
|
+
rescue => e
|
116
|
+
raise PDK::CLI::ExitWithError, _("Unable to remove '%{path}': %{message}") % {
|
117
|
+
path: path,
|
118
|
+
message: e.message,
|
119
|
+
}
|
120
|
+
end
|
121
|
+
|
122
|
+
private
|
123
|
+
|
124
|
+
# Loop through all the files to be modified and cache of unified diff of
|
125
|
+
# the changes to be made to each file.
|
126
|
+
#
|
127
|
+
# @raise [PDK::CLI::ExitWithError] if a file being modified isn't
|
128
|
+
# readable.
|
129
|
+
def calculate_diffs
|
130
|
+
@modified_files.each do |file|
|
131
|
+
next if @diff_cache.key?(file[:path])
|
132
|
+
|
133
|
+
unless File.readable?(file[:path])
|
134
|
+
raise PDK::CLI::ExitWithError, _("Unable to open '%{path}' for reading") % { path: file[:path] }
|
135
|
+
end
|
136
|
+
|
137
|
+
old_content = File.read(file[:path])
|
138
|
+
file_diff = unified_diff(file[:path], old_content, file[:content])
|
139
|
+
@diff_cache[file[:path]] = file_diff
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# Write or overwrite a file with the specified content.
|
144
|
+
#
|
145
|
+
# @param path [String] The path to be written to.
|
146
|
+
# @param content [String] The data to be written to the file.
|
147
|
+
#
|
148
|
+
# @raise [PDK::CLI::ExitWithError] if the file is not writeable.
|
149
|
+
def write_file(path, content)
|
150
|
+
FileUtils.mkdir_p(File.dirname(path))
|
151
|
+
PDK.logger.debug(_("writing '%{path}'") % { path: path })
|
152
|
+
PDK::Util::Filesystem.write_file(path, content)
|
153
|
+
rescue Errno::EACCES
|
154
|
+
raise PDK::CLI::ExitWithError, _("You do not have permission to write to '%{path}'") % { path: path }
|
155
|
+
end
|
156
|
+
|
157
|
+
# Generate a unified diff of the changes to be made to a file.
|
158
|
+
#
|
159
|
+
# @param path [String] The path to the file being diffed (only used to
|
160
|
+
# generate the diff header).
|
161
|
+
# @param old_content [String] The current content of the file.
|
162
|
+
# @param new_content [String] The new content of the file if the pending
|
163
|
+
# change is applied.
|
164
|
+
# @param lines_of_context [Integer] The maximum number of lines of
|
165
|
+
# context to include around the changed lines in the diff output
|
166
|
+
# (default: 3).
|
167
|
+
#
|
168
|
+
# @return [String] The unified diff of the pending changes to the file.
|
169
|
+
def unified_diff(path, old_content, new_content, lines_of_context = 3)
|
170
|
+
output = []
|
171
|
+
|
172
|
+
old_lines = old_content.split($INPUT_RECORD_SEPARATOR).map(&:chomp)
|
173
|
+
new_lines = new_content.split($INPUT_RECORD_SEPARATOR).map(&:chomp)
|
174
|
+
|
175
|
+
diffs = Diff::LCS.diff(old_lines, new_lines)
|
176
|
+
|
177
|
+
return nil if diffs.empty?
|
178
|
+
|
179
|
+
file_mtime = File.stat(path).mtime.localtime.strftime('%Y-%m-%d %H:%M:%S.%N %z')
|
180
|
+
now = Time.now.localtime.strftime('%Y-%m-%d %H:%M:%S.%N %z')
|
181
|
+
|
182
|
+
output << "--- #{path}\t#{file_mtime}"
|
183
|
+
output << "+++ #{path}.pdknew\t#{now}"
|
184
|
+
|
185
|
+
oldhunk = hunk = nil
|
186
|
+
file_length_difference = 0
|
187
|
+
|
188
|
+
diffs.each do |piece|
|
189
|
+
begin
|
190
|
+
hunk = Diff::LCS::Hunk.new(old_lines, new_lines, piece, lines_of_context, file_length_difference)
|
191
|
+
file_length_difference = hunk.file_length_difference
|
192
|
+
|
193
|
+
next unless oldhunk
|
194
|
+
|
195
|
+
# If the hunk overlaps with the oldhunk, merge them.
|
196
|
+
next if lines_of_context > 0 && hunk.merge(oldhunk)
|
197
|
+
|
198
|
+
output << oldhunk.diff(:unified)
|
199
|
+
ensure
|
200
|
+
oldhunk = hunk
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
output << oldhunk.diff(:unified)
|
205
|
+
|
206
|
+
output.join($INPUT_RECORD_SEPARATOR)
|
207
|
+
end
|
208
|
+
end
|
209
|
+
end
|
210
|
+
end
|