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
@@ -0,0 +1,48 @@
1
+ require 'pdk/generate/puppet_object'
2
+
3
+ module PDK
4
+ module Generate
5
+ class PuppetClass < PuppetObject
6
+ OBJECT_TYPE = :class
7
+
8
+ # Prepares the data needed to render the new Puppet class template.
9
+ #
10
+ # @return [Hash{Symbol => Object}] a hash of information that will be
11
+ # provided to the class and class spec templates during rendering.
12
+ def template_data
13
+ data = { name: object_name }
14
+
15
+ data
16
+ end
17
+
18
+ # Calculates the path to the .pp file that the new class will be written
19
+ # to.
20
+ #
21
+ # @return [String] the path where the new class will be written.
22
+ def target_object_path
23
+ @target_pp_path ||= begin
24
+ class_name_parts = object_name.split('::')[1..-1]
25
+ class_name_parts << 'init' if class_name_parts.empty?
26
+
27
+ "#{File.join(module_dir, 'manifests', *class_name_parts)}.pp"
28
+ end
29
+ end
30
+
31
+ # Calculates the path to the file where the tests for the new class will
32
+ # be written.
33
+ #
34
+ # @return [String] the path where the tests for the new class will be
35
+ # written.
36
+ def target_spec_path
37
+ @target_spec_path ||= begin
38
+ class_name_parts = object_name.split('::')
39
+
40
+ # drop the module name if the object name contains multiple parts
41
+ class_name_parts.delete_at(0) if class_name_parts.length > 1
42
+
43
+ "#{File.join(module_dir, 'spec', 'classes', *class_name_parts)}_spec.rb"
44
+ end
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,288 @@
1
+ require 'fileutils'
2
+
3
+ require 'pdk'
4
+ require 'pdk/logger'
5
+ require 'pdk/module/metadata'
6
+ require 'pdk/module/templatedir'
7
+ require 'pdk/template_file'
8
+ require 'pdk/util/filesystem'
9
+
10
+ module PDK
11
+ module Generate
12
+ class PuppetObject
13
+ attr_reader :module_dir
14
+ attr_reader :object_name
15
+ attr_reader :options
16
+
17
+ # Initialises the PDK::Generate::PuppetObject object.
18
+ #
19
+ # In general, this object should never be instantiated directly. Instead,
20
+ # one of the subclasses should be used e.g. PDK::Generate::Klass.
21
+ #
22
+ # New subclasses generally only need to inherit this class, set the
23
+ # OBJECT_TYPE constant and implement the {#template_data},
24
+ # {#target_object_path} and {#target_spec_path} methods.
25
+ #
26
+ # @param module_dir [String] The path to the module directory that the
27
+ # will contain the object.
28
+ # @param object_name [String] The name of the object.
29
+ # @param options [Hash{Symbol => Object}]
30
+ #
31
+ # @api public
32
+ def initialize(module_dir, object_name, options = {})
33
+ @module_dir = module_dir
34
+ @options = options
35
+ @object_name = object_name
36
+
37
+ if [:class, :defined_type].include?(object_type) # rubocop:disable Style/GuardClause
38
+ object_name_parts = object_name.split('::')
39
+
40
+ @object_name = if object_name_parts.first == module_name
41
+ object_name
42
+ else
43
+ [module_name, object_name].join('::')
44
+ end
45
+ end
46
+ end
47
+
48
+ # @abstract Subclass and implement {#template_data} to provide data to
49
+ # the templates during rendering. Implementations of this method should
50
+ # return a Hash!{Symbol => Object}.
51
+ def template_data
52
+ raise NotImplementedError
53
+ end
54
+
55
+ # @abstract Subclass and implement {#target_object_path}. Implementations
56
+ # of this method should return a String containing the destination path
57
+ # of the object being generated.
58
+ def target_object_path
59
+ raise NotImplementedError
60
+ end
61
+
62
+ # @abstract Subclass and implement {#target_type_path}. Implementations
63
+ # of this method should return a String containing the destination path
64
+ # of the additional object file being generated.
65
+ # @return [String] returns nil if there is no additional object file
66
+ def target_type_path
67
+ nil
68
+ end
69
+
70
+ # @abstract Subclass and implement {#target_spec_path}. Implementations
71
+ # of this method should return a String containing the destination path
72
+ # of the tests for the object being generated.
73
+ def target_spec_path
74
+ raise NotImplementedError
75
+ end
76
+
77
+ # @abstract Subclass and implement {#target_type_spec_path}. Implementations
78
+ # of this method should return a String containing the destination path
79
+ # of the tests for the object being generated.
80
+ def target_type_spec_path
81
+ nil
82
+ end
83
+
84
+ # Retrieves the type of the object being generated, e.g. :class,
85
+ # :defined_type, etc. This is specified in the subclass' OBJECT_TYPE
86
+ # constant.
87
+ #
88
+ # @return [Symbol] the type of the object being generated.
89
+ #
90
+ # @api private
91
+ def object_type
92
+ self.class::OBJECT_TYPE
93
+ end
94
+
95
+ # Check preconditions of this template group. By default this only makes sure that the target files do not
96
+ # already exist. Override this (and call super) to add your own preconditions.
97
+ #
98
+ # @raise [PDK::CLI::ExitWithError] if the target files already exist.
99
+ #
100
+ # @api public
101
+ def check_preconditions
102
+ [target_object_path, target_type_path, target_spec_path, target_type_spec_path].compact.each do |target_file|
103
+ next unless File.exist?(target_file)
104
+
105
+ raise PDK::CLI::ExitWithError, _("Unable to generate %{object_type}; '%{file}' already exists.") % {
106
+ file: target_file,
107
+ object_type: object_type,
108
+ }
109
+ end
110
+ end
111
+
112
+ # Check that the templates can be rendered. Find an appropriate template
113
+ # and create the target files from the template. This is the main entry
114
+ # point for the class.
115
+ #
116
+ # @raise [PDK::CLI::ExitWithError] if the target files already exist.
117
+ # @raise [PDK::CLI::FatalError] (see #render_file)
118
+ #
119
+ # @api public
120
+ def run
121
+ check_preconditions
122
+
123
+ with_templates do |template_path, config_hash|
124
+ data = template_data.merge(configs: config_hash)
125
+
126
+ render_file(target_object_path, template_path[:object], data)
127
+ render_file(target_type_path, template_path[:type], data) if template_path[:type]
128
+ render_file(target_spec_path, template_path[:spec], data) if template_path[:spec]
129
+ render_file(target_type_spec_path, template_path[:type_spec], data) if template_path[:type_spec]
130
+ end
131
+ end
132
+
133
+ # Render a file using the provided template and write it to disk.
134
+ #
135
+ # @param dest_path [String] The path that the rendered file should be
136
+ # written to. Any necessary directories will be automatically created.
137
+ # @param template_path [String] The path on disk to the file containing
138
+ # the template.
139
+ # @param data [Hash{Object => Object}] The data to be provided to the
140
+ # template when rendering.
141
+ #
142
+ # @raise [PDK::CLI::FatalError] if the parent directories to `dest_path`
143
+ # do not exist and could not be created.
144
+ # @raise [PDK::CLI::FatalError] if the rendered file could not be written
145
+ # to `dest_path`.
146
+ #
147
+ # @return [void]
148
+ #
149
+ # @api private
150
+ def render_file(dest_path, template_path, data)
151
+ write_file(dest_path) do
152
+ PDK::TemplateFile.new(template_path, data).render
153
+ end
154
+ end
155
+
156
+ # Write the result of the block to disk.
157
+ #
158
+ # @param dest_path [String] The path that the rendered file should be
159
+ # written to. Any necessary directories will be automatically created.
160
+ # @param &block [String] The content to be written
161
+ #
162
+ # @raise [PDK::CLI::FatalError] if the parent directories to `dest_path`
163
+ # do not exist and could not be created.
164
+ # @raise [PDK::CLI::FatalError] if the rendered file could not be written
165
+ # to `dest_path`.
166
+ #
167
+ # @return [void]
168
+ #
169
+ # @api private
170
+ def write_file(dest_path)
171
+ PDK.logger.info(_("Creating '%{file}' from template.") % { file: dest_path })
172
+
173
+ file_content = yield
174
+
175
+ begin
176
+ FileUtils.mkdir_p(File.dirname(dest_path))
177
+ rescue SystemCallError => e
178
+ raise PDK::CLI::FatalError, _("Unable to create directory '%{path}': %{message}") % {
179
+ path: File.dirname(dest_path),
180
+ message: e.message,
181
+ }
182
+ end
183
+
184
+ PDK::Util::Filesystem.write_file(dest_path, file_content)
185
+ rescue SystemCallError => e
186
+ raise PDK::CLI::FatalError, _("Unable to write to file '%{path}': %{message}") % {
187
+ path: dest_path,
188
+ message: e.message,
189
+ }
190
+ end
191
+
192
+ # Search the possible template directories in order of preference to find
193
+ # a template that can be used to render a new object of the specified
194
+ # type.
195
+ #
196
+ # @yieldparam template_paths [Hash{Symbol => String}] :object contains
197
+ # the path on disk to the template file for the object, :spec contains
198
+ # the path on disk to the template file for the tests for the object
199
+ # (if it exists).
200
+ # @yieldparam config_hash [Hash{Object => Object}] the contents of the
201
+ # :global key in the config_defaults.yml file.
202
+ #
203
+ # @raise [PDK::CLI::FatalError] if no suitable template could be found.
204
+ #
205
+ # @api private
206
+ def with_templates
207
+ templates.each do |template|
208
+ if template[:url].nil?
209
+ PDK.logger.debug(_('No %{dir_type} template specified; trying next template directory.') % { dir_type: template[:type] })
210
+ next
211
+ end
212
+
213
+ PDK::Module::TemplateDir.new(template[:url]) do |template_dir|
214
+ template_paths = template_dir.object_template_for(object_type)
215
+
216
+ if template_paths
217
+ config_hash = template_dir.object_config
218
+ yield template_paths, config_hash
219
+ # TODO: refactor to a search-and-execute form instead
220
+ return # work is done # rubocop:disable Lint/NonLocalExitFromIterator
221
+ elsif template[:allow_fallback]
222
+ PDK.logger.debug(_('Unable to find a %{type} template in %{url}; trying next template directory.') % { type: object_type, url: template[:url] })
223
+ else
224
+ raise PDK::CLI::FatalError, _('Unable to find the %{type} template in %{url}.') % { type: object_type, url: template[:url] }
225
+ end
226
+ end
227
+ end
228
+ rescue ArgumentError => e
229
+ raise PDK::CLI::ExitWithError, e
230
+ end
231
+
232
+ # Provides the possible template directory locations in the order in
233
+ # which they should be searched for a valid template.
234
+ #
235
+ # If a template-url has been specified on in the options hash (e.g. from
236
+ # a CLI parameter), then this template directory will be checked first
237
+ # and we do not fall back to the next possible template directory.
238
+ #
239
+ # If we have not been provided a specific template directory to use, we
240
+ # try the template specified in the module metadata (as set during
241
+ # PDK::Generate::Module) and fall back to the default template if
242
+ # necessary.
243
+ #
244
+ # @return [Array<Hash{Symbol => Object}>] an array of hashes. Each hash
245
+ # contains 3 keys: :type contains a String that describes the template
246
+ # directory, :url contains a String with the URL to the template
247
+ # directory, and :allow_fallback contains a Boolean that specifies if
248
+ # the lookup process should proceed to the next template directory if
249
+ # the template file is not in this template directory.
250
+ #
251
+ # @api private
252
+ def templates
253
+ @templates ||= [
254
+ { type: 'CLI', url: @options[:'template-url'], allow_fallback: false },
255
+ { type: 'metadata', url: module_metadata.data['template-url'], allow_fallback: true },
256
+ { type: 'default', url: PDK::Util.default_template_url, allow_fallback: false },
257
+ ]
258
+ end
259
+
260
+ # Retrieves the name of the module (without the forge username) from the
261
+ # module metadata.
262
+ #
263
+ # @raise (see #module_metadata)
264
+ # @return [String] The name of the module.
265
+ #
266
+ # @api private
267
+ def module_name
268
+ @module_name ||= module_metadata.data['name'].rpartition('-').last
269
+ end
270
+
271
+ # Parses the metadata.json file for the module.
272
+ #
273
+ # @raise [PDK::CLI::FatalError] if the metadata.json file does not exist,
274
+ # can not be read, or contains invalid metadata.
275
+ #
276
+ # @return [PDK::Module::Metadata] the parsed module metadata.
277
+ #
278
+ # @api private
279
+ def module_metadata
280
+ @module_metadata ||= begin
281
+ PDK::Module::Metadata.from_file(File.join(module_dir, 'metadata.json'))
282
+ rescue ArgumentError => e
283
+ raise PDK::CLI::FatalError, _("'%{dir}' does not contain valid Puppet module metadata: %{msg}") % { dir: module_dir, msg: e.message }
284
+ end
285
+ end
286
+ end
287
+ end
288
+ end
@@ -0,0 +1,86 @@
1
+ require 'pdk/generate/puppet_object'
2
+
3
+ module PDK
4
+ module Generate
5
+ class Task < PuppetObject
6
+ OBJECT_TYPE = :task
7
+
8
+ # Prepares the data needed to render the new task template.
9
+ #
10
+ # @return [Hash{Symbol => Object}] a hash of information that will be
11
+ # provided to the task template during rendering. Additionally, this hash
12
+ # (with the :name key removed) makes up the task metadata.
13
+ def template_data
14
+ {
15
+ name: object_name,
16
+ puppet_task_version: 1,
17
+ supports_noop: false,
18
+ description: options.fetch(:description, 'A short description of this task'),
19
+ parameters: {},
20
+ }
21
+ end
22
+
23
+ # Calculates the path to the file where the new task will be written.
24
+ #
25
+ # @return [String] the path to the task file.
26
+ def target_object_path
27
+ @target_object_path ||= File.join(module_dir, 'tasks', "#{task_name}.sh")
28
+ end
29
+
30
+ # Calculates the path to the file where the tests for the new task will
31
+ # be written.
32
+ #
33
+ # @return [nil] as there is currently no test framework for Tasks.
34
+ def target_spec_path
35
+ nil
36
+ end
37
+
38
+ def run
39
+ check_if_task_already_exists
40
+
41
+ super
42
+
43
+ write_task_metadata
44
+ end
45
+
46
+ # Checks that the task has not already been defined with a different
47
+ # extension.
48
+ #
49
+ # @raise [PDK::CLI::ExitWithError] if files with the same name as the
50
+ # task exist in the <module>/tasks/ directory
51
+ #
52
+ # @api private
53
+ def check_if_task_already_exists
54
+ error = _("A task named '%{name}' already exists in this module; defined in %{file}")
55
+ allowed_extensions = %w[.md .conf]
56
+
57
+ Dir.glob(File.join(module_dir, 'tasks', "#{task_name}.*")).each do |file|
58
+ next if allowed_extensions.include?(File.extname(file))
59
+
60
+ raise PDK::CLI::ExitWithError, error % { name: task_name, file: file }
61
+ end
62
+ end
63
+
64
+ # Writes the <module>/tasks/<task_name>.json metadata file for the task.
65
+ #
66
+ # @api private
67
+ def write_task_metadata
68
+ write_file(File.join(module_dir, 'tasks', "#{task_name}.json")) do
69
+ task_metadata = template_data.dup
70
+ task_metadata.delete(:name)
71
+ JSON.pretty_generate(task_metadata)
72
+ end
73
+ end
74
+
75
+ # Calculates the file name of the task files ('init' if the task has the
76
+ # same name as the module, otherwise use the specified task name).
77
+ #
78
+ # @return [String] the base name of the file(s) for the task.
79
+ #
80
+ # @api private
81
+ def task_name
82
+ (object_name == module_name) ? 'init' : object_name
83
+ end
84
+ end
85
+ end
86
+ end
data/lib/pdk/i18n.rb ADDED
@@ -0,0 +1,4 @@
1
+ require 'gettext-setup'
2
+
3
+ GettextSetup.initialize(File.absolute_path('../../locales', File.dirname(__FILE__)))
4
+ GettextSetup.negotiate_locale!(GettextSetup.candidate_locales)
data/lib/pdk/logger.rb ADDED
@@ -0,0 +1,28 @@
1
+ require 'logger'
2
+
3
+ module PDK
4
+ def self.logger
5
+ @logger ||= PDK::Logger.new
6
+ end
7
+
8
+ class Logger < ::Logger
9
+ def initialize
10
+ super(STDERR)
11
+
12
+ # TODO: Decide on output format.
13
+ self.formatter = proc do |severity, _datetime, _progname, msg|
14
+ "pdk (#{severity}): #{msg}\n"
15
+ end
16
+
17
+ self.level = ::Logger::INFO
18
+ end
19
+
20
+ def enable_debug_output
21
+ self.level = ::Logger::DEBUG
22
+ end
23
+
24
+ def debug?
25
+ level == ::Logger::DEBUG
26
+ end
27
+ end
28
+ end