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.
Files changed (81) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +826 -0
  3. data/LICENSE +201 -0
  4. data/README.md +133 -0
  5. data/exe/pdk +10 -0
  6. data/lib/pdk.rb +10 -0
  7. data/lib/pdk/answer_file.rb +121 -0
  8. data/lib/pdk/cli.rb +113 -0
  9. data/lib/pdk/cli/build.rb +76 -0
  10. data/lib/pdk/cli/bundle.rb +42 -0
  11. data/lib/pdk/cli/convert.rb +41 -0
  12. data/lib/pdk/cli/errors.rb +23 -0
  13. data/lib/pdk/cli/exec.rb +246 -0
  14. data/lib/pdk/cli/exec_group.rb +67 -0
  15. data/lib/pdk/cli/module.rb +14 -0
  16. data/lib/pdk/cli/module/build.rb +14 -0
  17. data/lib/pdk/cli/module/generate.rb +45 -0
  18. data/lib/pdk/cli/new.rb +17 -0
  19. data/lib/pdk/cli/new/class.rb +32 -0
  20. data/lib/pdk/cli/new/defined_type.rb +30 -0
  21. data/lib/pdk/cli/new/module.rb +41 -0
  22. data/lib/pdk/cli/new/provider.rb +27 -0
  23. data/lib/pdk/cli/new/task.rb +31 -0
  24. data/lib/pdk/cli/test.rb +12 -0
  25. data/lib/pdk/cli/test/unit.rb +88 -0
  26. data/lib/pdk/cli/update.rb +32 -0
  27. data/lib/pdk/cli/util.rb +193 -0
  28. data/lib/pdk/cli/util/command_redirector.rb +26 -0
  29. data/lib/pdk/cli/util/interview.rb +63 -0
  30. data/lib/pdk/cli/util/option_normalizer.rb +53 -0
  31. data/lib/pdk/cli/util/option_validator.rb +56 -0
  32. data/lib/pdk/cli/validate.rb +124 -0
  33. data/lib/pdk/generate.rb +11 -0
  34. data/lib/pdk/generate/defined_type.rb +49 -0
  35. data/lib/pdk/generate/module.rb +318 -0
  36. data/lib/pdk/generate/provider.rb +82 -0
  37. data/lib/pdk/generate/puppet_class.rb +48 -0
  38. data/lib/pdk/generate/puppet_object.rb +288 -0
  39. data/lib/pdk/generate/task.rb +86 -0
  40. data/lib/pdk/i18n.rb +4 -0
  41. data/lib/pdk/logger.rb +28 -0
  42. data/lib/pdk/module.rb +21 -0
  43. data/lib/pdk/module/build.rb +214 -0
  44. data/lib/pdk/module/convert.rb +209 -0
  45. data/lib/pdk/module/metadata.rb +193 -0
  46. data/lib/pdk/module/templatedir.rb +313 -0
  47. data/lib/pdk/module/update.rb +111 -0
  48. data/lib/pdk/module/update_manager.rb +210 -0
  49. data/lib/pdk/report.rb +112 -0
  50. data/lib/pdk/report/event.rb +357 -0
  51. data/lib/pdk/template_file.rb +89 -0
  52. data/lib/pdk/tests/unit.rb +213 -0
  53. data/lib/pdk/util.rb +271 -0
  54. data/lib/pdk/util/bundler.rb +253 -0
  55. data/lib/pdk/util/filesystem.rb +12 -0
  56. data/lib/pdk/util/git.rb +74 -0
  57. data/lib/pdk/util/puppet_version.rb +242 -0
  58. data/lib/pdk/util/ruby_version.rb +147 -0
  59. data/lib/pdk/util/vendored_file.rb +88 -0
  60. data/lib/pdk/util/version.rb +42 -0
  61. data/lib/pdk/util/windows.rb +13 -0
  62. data/lib/pdk/util/windows/api_types.rb +57 -0
  63. data/lib/pdk/util/windows/file.rb +36 -0
  64. data/lib/pdk/util/windows/string.rb +16 -0
  65. data/lib/pdk/validate.rb +14 -0
  66. data/lib/pdk/validate/base_validator.rb +209 -0
  67. data/lib/pdk/validate/metadata/metadata_json_lint.rb +86 -0
  68. data/lib/pdk/validate/metadata/metadata_syntax.rb +109 -0
  69. data/lib/pdk/validate/metadata_validator.rb +30 -0
  70. data/lib/pdk/validate/puppet/puppet_lint.rb +67 -0
  71. data/lib/pdk/validate/puppet/puppet_syntax.rb +112 -0
  72. data/lib/pdk/validate/puppet_validator.rb +30 -0
  73. data/lib/pdk/validate/ruby/rubocop.rb +77 -0
  74. data/lib/pdk/validate/ruby_validator.rb +29 -0
  75. data/lib/pdk/validate/tasks/metadata_lint.rb +126 -0
  76. data/lib/pdk/validate/tasks/name.rb +88 -0
  77. data/lib/pdk/validate/tasks_validator.rb +33 -0
  78. data/lib/pdk/version.rb +4 -0
  79. data/locales/config.yaml +21 -0
  80. data/locales/pdk.pot +1283 -0
  81. metadata +304 -0
data/lib/pdk/module.rb ADDED
@@ -0,0 +1,21 @@
1
+ require 'pathspec'
2
+
3
+ module PDK
4
+ module Module
5
+ DEFAULT_IGNORED = [
6
+ '/pkg/',
7
+ '.*',
8
+ '~*',
9
+ '/coverage',
10
+ '/checksums.json',
11
+ '/REVISION',
12
+ '/spec/fixtures/modules/',
13
+ '/vendor/',
14
+ ].freeze
15
+
16
+ def default_ignored_pathspec
17
+ @default_ignored_pathspec ||= PathSpec.new(DEFAULT_IGNORED)
18
+ end
19
+ module_function :default_ignored_pathspec
20
+ end
21
+ end
@@ -0,0 +1,214 @@
1
+ require 'fileutils'
2
+ require 'minitar'
3
+ require 'zlib'
4
+ require 'pathspec'
5
+ require 'find'
6
+ require 'pdk/module'
7
+ require 'pdk/tests/unit'
8
+
9
+ module PDK
10
+ module Module
11
+ class Build
12
+ def self.invoke(options = {})
13
+ new(options).build
14
+ end
15
+
16
+ attr_reader :module_dir
17
+ attr_reader :target_dir
18
+
19
+ def initialize(options = {})
20
+ @module_dir = File.expand_path(options[:module_dir] || Dir.pwd)
21
+ @target_dir = File.expand_path(options[:'target-dir'] || File.join(module_dir, 'pkg'))
22
+ end
23
+
24
+ # Read and parse the values from metadata.json for the module that is
25
+ # being built.
26
+ #
27
+ # @return [Hash{String => Object}] The hash of metadata values.
28
+ def metadata
29
+ @metadata ||= PDK::Module::Metadata.from_file(File.join(module_dir, 'metadata.json')).data
30
+ end
31
+
32
+ # Return the path where the built package file will be written to.
33
+ def package_file
34
+ @package_file ||= File.join(target_dir, "#{release_name}.tar.gz")
35
+ end
36
+
37
+ # Build a module package from a module directory.
38
+ #
39
+ # @return [String] The path to the built package file.
40
+ def build
41
+ create_build_dir
42
+
43
+ stage_module_in_build_dir
44
+ build_package
45
+
46
+ package_file
47
+ ensure
48
+ cleanup_build_dir
49
+ end
50
+
51
+ # Verify if there is an existing package in the target directory and prompts
52
+ # the user if they want to overwrite it.
53
+ def package_already_exists?
54
+ File.exist? package_file
55
+ end
56
+
57
+ # Check if the module is PDK Compatible. If not, then prompt the user if
58
+ # they want to run PDK Convert.
59
+ def module_pdk_compatible?
60
+ ['pdk-version', 'template-url'].any? { |key| metadata.key?(key) }
61
+ end
62
+
63
+ # Return the path to the temporary build directory, which will be placed
64
+ # inside the target directory and match the release name (see #release_name).
65
+ def build_dir
66
+ @build_dir ||= File.join(target_dir, release_name)
67
+ end
68
+
69
+ # Create a temporary build directory where the files to be included in
70
+ # the package will be staged before building the tarball.
71
+ #
72
+ # If the directory already exists, remove it first.
73
+ def create_build_dir
74
+ cleanup_build_dir
75
+
76
+ FileUtils.mkdir_p(build_dir)
77
+ end
78
+
79
+ # Remove the temporary build directory and all its contents from disk.
80
+ #
81
+ # @return nil.
82
+ def cleanup_build_dir
83
+ FileUtils.rm_rf(build_dir, secure: true)
84
+ end
85
+
86
+ # Combine the module name and version into a Forge-compatible dash
87
+ # separated string.
88
+ #
89
+ # @return [String] The module name and version, joined by a dash.
90
+ def release_name
91
+ @release_name ||= [
92
+ metadata['name'],
93
+ metadata['version'],
94
+ ].join('-')
95
+ end
96
+
97
+ # Iterate through all the files and directories in the module and stage
98
+ # them into the temporary build directory (unless ignored).
99
+ #
100
+ # @return nil
101
+ def stage_module_in_build_dir
102
+ Find.find(module_dir) do |path|
103
+ next if path == module_dir
104
+
105
+ ignored_path?(path) ? Find.prune : stage_path(path)
106
+ end
107
+ end
108
+
109
+ # Stage a file or directory from the module into the build directory.
110
+ #
111
+ # @param path [String] The path to the file or directory.
112
+ #
113
+ # @return nil.
114
+ def stage_path(path)
115
+ relative_path = Pathname.new(path).relative_path_from(Pathname.new(module_dir))
116
+ dest_path = File.join(build_dir, relative_path)
117
+
118
+ if File.directory?(path)
119
+ FileUtils.mkdir_p(dest_path, mode: File.stat(path).mode)
120
+ elsif File.symlink?(path)
121
+ warn_symlink(path)
122
+ else
123
+ FileUtils.cp(path, dest_path, preserve: true)
124
+ end
125
+ end
126
+
127
+ # Check if the given path matches one of the patterns listed in the
128
+ # ignore file.
129
+ #
130
+ # @param path [String] The path to be checked.
131
+ #
132
+ # @return [Boolean] true if the path matches and should be ignored.
133
+ def ignored_path?(path)
134
+ path = path.to_s + '/' if File.directory?(path)
135
+
136
+ !ignored_files.match_paths([path], module_dir).empty?
137
+ end
138
+
139
+ # Warn the user about a symlink that would have been included in the
140
+ # built package.
141
+ #
142
+ # @param path [String] The relative or absolute path to the symlink.
143
+ #
144
+ # @return nil.
145
+ def warn_symlink(path)
146
+ symlink_path = Pathname.new(path)
147
+ module_path = Pathname.new(module_dir)
148
+
149
+ PDK.logger.warn _('Symlinks in modules are not supported and will not be included in the package. Please investigate symlink %{from} -> %{to}.') % {
150
+ from: symlink_path.relative_path_from(module_path),
151
+ to: symlink_path.realpath.relative_path_from(module_path),
152
+ }
153
+ end
154
+
155
+ # Creates a gzip compressed tarball of the build directory.
156
+ #
157
+ # If the destination package already exists, it will be removed before
158
+ # creating the new tarball.
159
+ #
160
+ # @return nil.
161
+ def build_package
162
+ FileUtils.rm_f(package_file)
163
+
164
+ Dir.chdir(target_dir) do
165
+ Zlib::GzipWriter.open(package_file) do |package_fd|
166
+ Minitar.pack(release_name, package_fd)
167
+ end
168
+ end
169
+ end
170
+
171
+ # Select the most appropriate ignore file in the module directory.
172
+ #
173
+ # In order of preference, we first try `.pdkignore`, then `.pmtignore`
174
+ # and finally `.gitignore`.
175
+ #
176
+ # @return [String] The path to the file containing the patterns of file
177
+ # paths to ignore.
178
+ def ignore_file
179
+ @ignore_file ||= [
180
+ File.join(module_dir, '.pdkignore'),
181
+ File.join(module_dir, '.pmtignore'),
182
+ File.join(module_dir, '.gitignore'),
183
+ ].find { |file| File.file?(file) && File.readable?(file) }
184
+ end
185
+
186
+ # Instantiate a new PathSpec class and populate it with the pattern(s) of
187
+ # files to be ignored.
188
+ #
189
+ # @return [PathSpec] The populated ignore path matcher.
190
+ def ignored_files
191
+ @ignored_files ||=
192
+ begin
193
+ ignored = if ignore_file.nil?
194
+ PathSpec.new
195
+ else
196
+ fd = File.open(ignore_file, 'rb:UTF-8')
197
+ data = fd.read
198
+ fd.close
199
+
200
+ PathSpec.new(data)
201
+ end
202
+
203
+ if File.realdirpath(target_dir).start_with?(File.realdirpath(module_dir))
204
+ ignored = ignored.add("\/#{File.basename(target_dir)}\/")
205
+ end
206
+
207
+ PDK::Module::DEFAULT_IGNORED.each { |r| ignored.add(r) }
208
+
209
+ ignored
210
+ end
211
+ end
212
+ end
213
+ end
214
+ end
@@ -0,0 +1,209 @@
1
+ require 'pdk/generate/module'
2
+ require 'pdk/module/update_manager'
3
+ require 'pdk/util'
4
+ require 'pdk/report'
5
+
6
+ module PDK
7
+ module Module
8
+ class Convert
9
+ def self.invoke(options)
10
+ new(options).run
11
+ end
12
+
13
+ attr_reader :options
14
+
15
+ def initialize(options = {})
16
+ @options = options
17
+ end
18
+
19
+ def run
20
+ stage_changes!
21
+
22
+ unless update_manager.changes?
23
+ PDK::Report.default_target.puts(_('No changes required.'))
24
+ return
25
+ end
26
+
27
+ print_summary
28
+
29
+ full_report('convert_report.txt') unless update_manager.changes[:modified].empty?
30
+
31
+ return if noop?
32
+
33
+ unless force?
34
+ PDK.logger.info _(
35
+ 'Module conversion is a potentially destructive action. ' \
36
+ 'Ensure that you have committed your module to a version control ' \
37
+ 'system or have a backup, and review the changes above before continuing.',
38
+ )
39
+ continue = PDK::CLI::Util.prompt_for_yes(_('Do you want to continue and make these changes to your module?'))
40
+ return unless continue
41
+ end
42
+
43
+ # Remove these files straight away as these changes are not something that the user needs to review.
44
+ if needs_bundle_update?
45
+ update_manager.unlink_file('Gemfile.lock')
46
+ update_manager.unlink_file(File.join('.bundle', 'config'))
47
+ end
48
+
49
+ update_manager.sync_changes!
50
+
51
+ PDK::Util::Bundler.ensure_bundle! if needs_bundle_update?
52
+
53
+ print_result 'Convert completed'
54
+ end
55
+
56
+ def noop?
57
+ options[:noop]
58
+ end
59
+
60
+ def force?
61
+ options[:force]
62
+ end
63
+
64
+ def needs_bundle_update?
65
+ update_manager.changed?('Gemfile')
66
+ end
67
+
68
+ def stage_changes!
69
+ metadata_path = 'metadata.json'
70
+
71
+ PDK::Module::TemplateDir.new(template_url, nil, false) do |templates|
72
+ new_metadata = update_metadata(metadata_path, templates.metadata)
73
+ templates.module_metadata = new_metadata.data unless new_metadata.nil?
74
+
75
+ if options[:noop] && new_metadata.nil?
76
+ update_manager.add_file(metadata_path, '')
77
+ elsif File.file?(metadata_path)
78
+ update_manager.modify_file(metadata_path, new_metadata.to_json)
79
+ else
80
+ update_manager.add_file(metadata_path, new_metadata.to_json)
81
+ end
82
+
83
+ templates.render do |file_path, file_content, file_status|
84
+ if file_status == :unmanage
85
+ PDK.logger.debug(_("skipping '%{path}'") % { path: file_path })
86
+ elsif file_status == :delete
87
+ update_manager.remove_file(file_path)
88
+ elsif file_status == :manage
89
+ if File.exist? file_path
90
+ update_manager.modify_file(file_path, file_content)
91
+ else
92
+ update_manager.add_file(file_path, file_content)
93
+ end
94
+ end
95
+ end
96
+ end
97
+ rescue ArgumentError => e
98
+ raise PDK::CLI::ExitWithError, e
99
+ end
100
+
101
+ def update_manager
102
+ @update_manager ||= PDK::Module::UpdateManager.new
103
+ end
104
+
105
+ def template_url
106
+ @template_url ||= options.fetch(:'template-url', PDK::Util.default_template_url)
107
+ end
108
+
109
+ def update_metadata(metadata_path, template_metadata)
110
+ if File.file?(metadata_path)
111
+ unless File.readable?(metadata_path)
112
+ raise PDK::CLI::ExitWithError, _('Unable to update module metadata; %{path} exists but it is not readable.') % {
113
+ path: metadata_path,
114
+ }
115
+ end
116
+
117
+ begin
118
+ metadata = PDK::Module::Metadata.from_file(metadata_path)
119
+ new_values = PDK::Module::Metadata::DEFAULTS.select do |key, _|
120
+ !metadata.data.key?(key) || metadata.data[key].nil? ||
121
+ (key == 'requirements' && metadata.data[key].empty?)
122
+ end
123
+ metadata.update!(new_values)
124
+ rescue ArgumentError
125
+ metadata = PDK::Generate::Module.prepare_metadata(options) unless options[:noop]
126
+ end
127
+ elsif File.exist?(metadata_path)
128
+ raise PDK::CLI::ExitWithError, _('Unable to update module metadata; %{path} exists but it is not a file.') % {
129
+ path: metadata_path,
130
+ }
131
+ else
132
+ return nil if options[:noop]
133
+
134
+ project_dir = File.basename(Dir.pwd)
135
+ options[:module_name] = project_dir.split('-', 2).compact[-1]
136
+ options[:prompt] = false
137
+ options[:'skip-interview'] = true if options[:force]
138
+
139
+ metadata = PDK::Generate::Module.prepare_metadata(options)
140
+ end
141
+
142
+ metadata.update!(template_metadata)
143
+ metadata
144
+ end
145
+
146
+ def summary
147
+ summary = {}
148
+ update_manager.changes.each do |category, update_category|
149
+ if update_category.respond_to?(:keys)
150
+ updated_files = update_category.keys
151
+ else
152
+ begin
153
+ updated_files = update_category.map { |file| file[:path] }
154
+ rescue TypeError
155
+ updated_files = update_category.to_a
156
+ end
157
+ end
158
+
159
+ summary[category] = updated_files
160
+ end
161
+
162
+ summary
163
+ end
164
+
165
+ def print_summary
166
+ footer = false
167
+
168
+ summary.keys.each do |category|
169
+ next if summary[category].empty?
170
+
171
+ PDK::Report.default_target.puts(_("\n%{banner}") % { banner: generate_banner("Files to be #{category}", 40) })
172
+ PDK::Report.default_target.puts(summary[category])
173
+ footer = true
174
+ end
175
+
176
+ PDK::Report.default_target.puts(_("\n%{banner}") % { banner: generate_banner('', 40) }) if footer
177
+ end
178
+
179
+ def print_result(banner_text)
180
+ PDK::Report.default_target.puts(_("\n%{banner}") % { banner: generate_banner(banner_text, 40) })
181
+ summary_to_print = summary.map { |k, v| "#{v.length} files #{k}" unless v.empty? }.compact
182
+ PDK::Report.default_target.puts(_("\n%{summary}\n\n") % { summary: "#{summary_to_print.join(', ')}." })
183
+ end
184
+
185
+ def full_report(path)
186
+ File.open(path, 'w') do |f|
187
+ f.write("/* Report generated by PDK at #{Time.now} */")
188
+ update_manager.changes[:modified].each do |_, diff|
189
+ f.write("\n\n\n" + diff)
190
+ end
191
+ f.write("\n")
192
+ end
193
+ PDK::Report.default_target.puts(_("\nYou can find a report of differences in %{path}.\n\n") % { path: path })
194
+ end
195
+
196
+ def generate_banner(text, width = 80)
197
+ padding = width - text.length
198
+ banner = ''
199
+ padding_char = '-'
200
+
201
+ (padding / 2.0).ceil.times { banner << padding_char }
202
+ banner << text
203
+ (padding / 2.0).floor.times { banner << padding_char }
204
+
205
+ banner
206
+ end
207
+ end
208
+ end
209
+ end
@@ -0,0 +1,193 @@
1
+ require 'json'
2
+ require 'pdk/util/filesystem'
3
+
4
+ module PDK
5
+ module Module
6
+ class Metadata
7
+ attr_accessor :data
8
+
9
+ include PDK::Util::Filesystem
10
+
11
+ OPERATING_SYSTEMS = {
12
+ 'RedHat based Linux' => [
13
+ {
14
+ 'operatingsystem' => 'CentOS',
15
+ 'operatingsystemrelease' => ['7'],
16
+ },
17
+ {
18
+ 'operatingsystem' => 'OracleLinux',
19
+ 'operatingsystemrelease' => ['7'],
20
+ },
21
+ {
22
+ 'operatingsystem' => 'RedHat',
23
+ 'operatingsystemrelease' => ['7'],
24
+ },
25
+ {
26
+ 'operatingsystem' => 'Scientific',
27
+ 'operatingsystemrelease' => ['7'],
28
+ },
29
+ ],
30
+ 'Debian based Linux' => [
31
+ {
32
+ 'operatingsystem' => 'Debian',
33
+ 'operatingsystemrelease' => ['8'],
34
+ },
35
+ {
36
+ 'operatingsystem' => 'Ubuntu',
37
+ 'operatingsystemrelease' => ['16.04'],
38
+ },
39
+ ],
40
+ 'Fedora' => {
41
+ 'operatingsystem' => 'Fedora',
42
+ 'operatingsystemrelease' => ['25'],
43
+ },
44
+ 'OSX' => {
45
+ 'operatingsystem' => 'Darwin',
46
+ 'operatingsystemrelease' => ['16'],
47
+ },
48
+ 'SLES' => {
49
+ 'operatingsystem' => 'SLES',
50
+ 'operatingsystemrelease' => ['12'],
51
+ },
52
+ 'Solaris' => {
53
+ 'operatingsystem' => 'Solaris',
54
+ 'operatingsystemrelease' => ['11'],
55
+ },
56
+ 'Windows' => {
57
+ 'operatingsystem' => 'windows',
58
+ 'operatingsystemrelease' => ['2008 R2', '2012 R2', '10'],
59
+ },
60
+ }.freeze
61
+
62
+ DEFAULT_OPERATING_SYSTEMS = [
63
+ 'RedHat based Linux',
64
+ 'Debian based Linux',
65
+ 'Windows',
66
+ ].freeze
67
+
68
+ DEFAULTS = {
69
+ 'name' => nil,
70
+ 'version' => '0.1.0',
71
+ 'author' => nil,
72
+ 'summary' => '',
73
+ 'license' => 'Apache-2.0',
74
+ 'source' => '',
75
+ 'project_page' => nil,
76
+ 'issues_url' => nil,
77
+ 'dependencies' => [],
78
+ 'data_provider' => nil,
79
+ 'operatingsystem_support' => DEFAULT_OPERATING_SYSTEMS.map { |os_name|
80
+ OPERATING_SYSTEMS[os_name]
81
+ }.flatten,
82
+ 'requirements' => [
83
+ { 'name' => 'puppet', 'version_requirement' => '>= 4.10.0 < 7.0.0' },
84
+ ],
85
+ }.freeze
86
+
87
+ def initialize(params = {})
88
+ @data = DEFAULTS.dup
89
+ update!(params) if params
90
+ end
91
+
92
+ def self.from_file(metadata_json_path)
93
+ if metadata_json_path.nil?
94
+ raise ArgumentError, _('Cannot read metadata from file: no path to file was given.')
95
+ end
96
+
97
+ unless File.file?(metadata_json_path)
98
+ raise ArgumentError, _("'%{file}' does not exist or is not a file.") % { file: metadata_json_path }
99
+ end
100
+
101
+ unless File.readable?(metadata_json_path)
102
+ raise ArgumentError, _("Unable to open '%{file}' for reading.") % { file: metadata_json_path }
103
+ end
104
+
105
+ begin
106
+ data = JSON.parse(File.read(metadata_json_path))
107
+ rescue JSON::JSONError => e
108
+ raise ArgumentError, _('Invalid JSON in metadata.json: %{msg}') % { msg: e.message }
109
+ end
110
+
111
+ new(data)
112
+ end
113
+
114
+ def update!(data)
115
+ # TODO: validate all data
116
+ process_name(data) if data['name']
117
+ @data.merge!(data)
118
+ self
119
+ end
120
+
121
+ def to_json
122
+ JSON.pretty_generate(@data.dup.delete_if { |_key, value| value.nil? })
123
+ end
124
+
125
+ def write!(path)
126
+ write_file(path, to_json)
127
+ end
128
+
129
+ def forge_ready?
130
+ missing_fields.empty?
131
+ end
132
+
133
+ def interview_for_forge!
134
+ PDK::Generate::Module.module_interview(self, only_ask: missing_fields)
135
+ end
136
+
137
+ def validate_puppet_version_requirement!
138
+ msgs = {
139
+ no_reqs: _('Module metadata does not contain any requirements.'),
140
+ no_puppet_req: _('Module metadata does not contain a "puppet" requirement.'),
141
+ no_puppet_ver: _('The "puppet" requirement in module metadata does not specify a "version_requirement".'),
142
+ }
143
+
144
+ raise ArgumentError, msgs[:no_reqs] unless @data.key?('requirements')
145
+ raise ArgumentError, msgs[:no_puppet_req] if puppet_requirement.nil?
146
+ raise ArgumentError, msgs[:no_puppet_ver] unless puppet_requirement.key?('version_requirement')
147
+ raise ArgumentError, msgs[:no_puppet_ver] if puppet_requirement['version_requirement'].empty?
148
+ end
149
+
150
+ def puppet_requirement
151
+ @data['requirements'].find do |r|
152
+ r.key?('name') && r['name'] == 'puppet'
153
+ end
154
+ end
155
+
156
+ private
157
+
158
+ def missing_fields
159
+ fields = DEFAULTS.keys - %w[data_provider requirements dependencies]
160
+ fields.select { |key| @data[key].nil? || @data[key].empty? }
161
+ end
162
+
163
+ # Do basic validation and parsing of the name parameter.
164
+ def process_name(data)
165
+ validate_name(data['name'])
166
+ author, _modname = data['name'].split(%r{[-/]}, 2)
167
+
168
+ data['author'] ||= author if @data['author'] == DEFAULTS['author']
169
+ end
170
+
171
+ # Validates that the given module name is both namespaced and well-formed.
172
+ def validate_name(name)
173
+ return if name =~ %r{\A[a-z0-9]+[-\/][a-z][a-z0-9_]*\Z}i
174
+
175
+ namespace, modname = name.split(%r{[-/]}, 2)
176
+ modname = :namespace_missing if namespace == ''
177
+
178
+ err = case modname
179
+ when nil, '', :namespace_missing
180
+ _('Field must be a dash-separated user name and module name.')
181
+ when %r{[^a-z0-9_]}i
182
+ _('Module name must contain only alphanumeric or underscore characters.')
183
+ when %r{^[^a-z]}i
184
+ _('Module name must begin with a letter.')
185
+ else
186
+ _('Namespace must contain only alphanumeric characters.')
187
+ end
188
+
189
+ raise ArgumentError, _("Invalid 'name' field in metadata.json: %{err}") % { err: err }
190
+ end
191
+ end
192
+ end
193
+ end