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