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