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/report.rb ADDED
@@ -0,0 +1,112 @@
1
+ require 'rexml/document'
2
+ require 'time'
3
+ require 'pdk/report/event'
4
+ require 'socket'
5
+
6
+ module PDK
7
+ class Report
8
+ # @return [Array<String>] the list of supported report formats.
9
+ def self.formats
10
+ @report_formats ||= %w[junit text].freeze
11
+ end
12
+
13
+ # @return [Symbol] the method name of the default report format.
14
+ def self.default_format
15
+ :write_text
16
+ end
17
+
18
+ # @return [#write] the default target to write the report to.
19
+ def self.default_target
20
+ $stdout
21
+ end
22
+
23
+ # Memoised access to the report event storage hash.
24
+ #
25
+ # The keys of the Hash are the source names of the Events (see
26
+ # PDK::Report::Event#source).
27
+ #
28
+ # @example accessing events from the puppet-lint validator
29
+ # report = PDK::Report.new
30
+ # report.events['puppet-lint']
31
+ #
32
+ # @return [Hash{String=>Array<PDK::Report::Event>}] the events stored in
33
+ # the repuort.
34
+ def events
35
+ @events ||= {}
36
+ end
37
+
38
+ # Create a new PDK::Report::Event from a hash of values and add it to the
39
+ # report.
40
+ #
41
+ # @param data [Hash] (see PDK::Report::Event#initialize)
42
+ def add_event(data)
43
+ (events[data[:source]] ||= []) << PDK::Report::Event.new(data)
44
+ end
45
+
46
+ # Renders the report as a JUnit XML document.
47
+ #
48
+ # @param target [#write] an IO object that the report will be written to.
49
+ # Defaults to PDK::Report.default_target.
50
+ def write_junit(target = self.class.default_target)
51
+ # Open a File Object for IO if target is a string containing a filename or path
52
+ target = File.open(target, 'w') if target.is_a? String
53
+
54
+ document = REXML::Document.new
55
+ document << REXML::XMLDecl.new
56
+ testsuites = REXML::Element.new('testsuites')
57
+
58
+ id = 0
59
+ events.each do |testsuite_name, testcases|
60
+ testsuite = REXML::Element.new('testsuite')
61
+ testsuite.attributes['name'] = testsuite_name
62
+ testsuite.attributes['tests'] = testcases.length
63
+ testsuite.attributes['errors'] = testcases.select(&:error?).length
64
+ testsuite.attributes['failures'] = testcases.select(&:failure?).length
65
+ testsuite.attributes['skipped'] = testcases.select(&:skipped?).length
66
+ testsuite.attributes['time'] = 0
67
+ testsuite.attributes['timestamp'] = Time.now.strftime('%Y-%m-%dT%H:%M:%S')
68
+ testsuite.attributes['hostname'] = Socket.gethostname
69
+ testsuite.attributes['id'] = id
70
+ testsuite.attributes['package'] = testsuite_name
71
+ testsuite.add_element('properties')
72
+ testcases.each { |r| testsuite.elements << r.to_junit }
73
+ testsuite.add_element('system-out')
74
+ testsuite.add_element('system-err')
75
+
76
+ testsuites.elements << testsuite
77
+ id += 1
78
+ end
79
+
80
+ document.elements << testsuites
81
+ document.write(target, 2)
82
+ ensure
83
+ target.close if target.is_a? File
84
+ end
85
+
86
+ # Renders the report as plain text.
87
+ #
88
+ # This report is designed for interactive use by a human and so excludes
89
+ # all passing events in order to be consise.
90
+ #
91
+ # @param target [#write] an IO object that the report will be written to.
92
+ # Defaults to PDK::Report.default_target.
93
+ def write_text(target = self.class.default_target)
94
+ # Open a File Object for IO if target is a string containing a filename or path
95
+ target = File.open(target, 'w') if target.is_a? String
96
+ coverage_report = nil
97
+
98
+ events.each do |_tool, tool_events|
99
+ tool_events.each do |event|
100
+ if event.rspec_puppet_coverage?
101
+ coverage_report = event.to_text
102
+ else
103
+ target.puts(event.to_text) unless event.pass?
104
+ end
105
+ end
106
+ end
107
+ ensure
108
+ target.puts "\n#{coverage_report}" if coverage_report
109
+ target.close if target.is_a? File
110
+ end
111
+ end
112
+ end
@@ -0,0 +1,357 @@
1
+ require 'rexml/document'
2
+ require 'pathname'
3
+
4
+ module PDK
5
+ class Report
6
+ class Event
7
+ # @return [String] The path to the file that the event is in reference
8
+ # to.
9
+ attr_reader :file
10
+
11
+ # @return [Integer] The line number in the file that the event is in
12
+ # reference to.
13
+ attr_reader :line
14
+
15
+ # @return [Integer] The column number in the line of the file that the
16
+ # event is in reference to.
17
+ attr_reader :column
18
+
19
+ # @return [String] The name of the source of the event (usually the name
20
+ # of the validation or testing tool that generated the event).
21
+ attr_reader :source
22
+
23
+ # @return [String] A freeform String containing a human readable message
24
+ # describing the event.
25
+ attr_reader :message
26
+
27
+ # @return [String] The severity of the event as reported by the
28
+ # underlying tool.
29
+ attr_reader :severity
30
+
31
+ # @return [String] The name of the test that generated the event.
32
+ attr_reader :test
33
+
34
+ # @return [Symbol] The state of the event. :passed, :failure, :error, or
35
+ # :skipped.
36
+ attr_reader :state
37
+
38
+ # @return [Array] Array of full stack trace lines associated with event
39
+ attr_reader :trace
40
+
41
+ # Initailises a new PDK::Report::Event object.
42
+ #
43
+ # @param data [Hash{Symbol=>Object}
44
+ # @option data [String] :file (see #file)
45
+ # @option data [Integer] :line (see #line)
46
+ # @option data [Integer] :column (see #column)
47
+ # @option data [String] :source (see #source)
48
+ # @option data [String] :message (see #message)
49
+ # @option data [String] :severity (see #severity)
50
+ # @option data [String] :test (see #test)
51
+ # @option data [Symbol] :state (see #state)
52
+ # @option data [Array] :trace (see #trace)
53
+ #
54
+ # @raise [ArgumentError] (see #sanitise_data)
55
+ def initialize(data)
56
+ sanitise_data(data).each do |key, value|
57
+ instance_variable_set("@#{key}", value)
58
+ end
59
+ end
60
+
61
+ # Checks if the event is the result of a passing test.
62
+ #
63
+ # @return [Boolean] true if the test passed, otherwise false.
64
+ def pass?
65
+ state == :passed
66
+ end
67
+
68
+ # Checks if the event is the result of a test that could not complete due
69
+ # to an error.
70
+ #
71
+ # @return [Boolean] true if the test did not complete, otherwise false.
72
+ def error?
73
+ state == :error
74
+ end
75
+
76
+ # Checks if the event is the result of a failing test.
77
+ #
78
+ # @return [Boolean] true if the test failed, otherwise false.
79
+ def failure?
80
+ state == :failure
81
+ end
82
+
83
+ # Checks if the event is the result of test that was not run.
84
+ # This includes pending tests (that are run but have an expected failure result).
85
+ #
86
+ # @return [Boolean] true if the test was skipped, otherwise false.
87
+ def skipped?
88
+ state == :skipped
89
+ end
90
+
91
+ # Checks if the event stores the result of an rspec-puppet coverage
92
+ # check.
93
+ #
94
+ # Due to the implementation details of this check, the `file` value for
95
+ # this event will always point to the coverage.rb file in rspec-puppet,
96
+ # making it easy to filter out.
97
+ #
98
+ # @return [Boolean] true if the event contains rspec-puppet coverage
99
+ # results.
100
+ def rspec_puppet_coverage?
101
+ @rspec_puppet_coverage_pattern ||= File.join('**', 'lib', 'rspec-puppet', 'coverage.rb')
102
+ source == 'rspec' && File.fnmatch?(@rspec_puppet_coverage_pattern, File.expand_path(file))
103
+ end
104
+
105
+ # Renders the event in a clang style text format.
106
+ #
107
+ # @return [String] The rendered event.
108
+ def to_text
109
+ return message if rspec_puppet_coverage?
110
+
111
+ location = [file, line, column].compact.join(':')
112
+ location = nil if location.empty?
113
+
114
+ # TODO: maybe add trace
115
+ header = [severity, source, location, message].compact.join(': ')
116
+ if source == 'rspec'
117
+ result = [header, " #{test}"]
118
+ context = context_lines
119
+ unless context.nil?
120
+ result << ' Failure/Error:'
121
+ result.concat(context)
122
+ result << "\n"
123
+ end
124
+
125
+ result.compact.join("\n")
126
+ else
127
+ header
128
+ end
129
+ end
130
+
131
+ # Renders the event as a JUnit XML testcase.
132
+ #
133
+ # @return [REXML::Element] The rendered event.
134
+ def to_junit
135
+ testcase = REXML::Element.new('testcase')
136
+ testcase.attributes['classname'] = [source, test].compact.join('.')
137
+ testcase.attributes['name'] = [file, line, column].compact.join(':')
138
+ testcase.attributes['time'] = 0
139
+
140
+ if failure?
141
+ failure = REXML::Element.new('failure')
142
+ failure.attributes['type'] = severity
143
+ failure.attributes['message'] = message
144
+ failure.text = to_text
145
+ testcase.elements << failure
146
+ elsif skipped?
147
+ testcase.add_element('skipped')
148
+ end
149
+
150
+ testcase
151
+ end
152
+
153
+ private
154
+
155
+ # Processes the data hash used to initialise the event, validating and
156
+ # munging the values as necessary.
157
+ #
158
+ # @param data [Hash{Symbol => Object}] (see #initialize)
159
+ #
160
+ # @return [Hash{Symbol => String}] A copy of the data hash passed to the
161
+ # method with sanitised values.
162
+ #
163
+ # @raise [ArgumentError] (see #sanitise_file)
164
+ # @raise [ArgumentError] (see #sanitise_state)
165
+ # @raise [ArgumentError] (see #sanitise_source)
166
+ def sanitise_data(data)
167
+ result = data.dup
168
+ data.each do |key, value|
169
+ key = key.to_sym unless key.is_a?(Symbol)
170
+ method = "sanitise_#{key}"
171
+ result[key] = send(method, value) if respond_to?(method, true)
172
+ end
173
+
174
+ result
175
+ end
176
+
177
+ # Munges and validates the file path used to instantiate the event.
178
+ #
179
+ # If the path is an absolute path, it will be rewritten so that it is
180
+ # relative to the module root instead.
181
+ #
182
+ # @param value [String] The path to the file that the event is
183
+ # describing.
184
+ #
185
+ # @return [String] The path to the file, relative to the module root.
186
+ #
187
+ # @raise [ArgumentError] if the value is nil, an empty String, or not
188
+ # a String.
189
+ def sanitise_file(value)
190
+ if value.nil? || (value.is_a?(String) && value.empty?)
191
+ raise ArgumentError, _('File not specified.')
192
+ end
193
+
194
+ unless value.is_a?(String)
195
+ raise ArgumentError, _('File must be a String.')
196
+ end
197
+
198
+ path = Pathname.new(value)
199
+
200
+ if path.absolute?
201
+ module_root = Pathname.new(PDK::Util.module_root)
202
+ path = path.relative_path_from(module_root).to_path
203
+ path << '/' if path == '.'
204
+ path
205
+ else
206
+ path.to_path
207
+ end
208
+ end
209
+
210
+ # Munges and validates the state of the event.
211
+ #
212
+ # The valid event states are:
213
+ # :passed - The event represents a passing test.
214
+ # :error - The event represents a test that could not be completed due
215
+ # to an unexpected error.
216
+ # :failure - The event represents a failing test.
217
+ # :skipped - The event represents a test that was skipped.
218
+ #
219
+ # @param value [Symbol, String] The state of the event. If passed as
220
+ # a String, it will be turned into a Symbol before validation.
221
+ #
222
+ # @return [Symbol] The sanitised state type.
223
+ #
224
+ # @raise [ArgumentError] if the value is nil, an empty String, or not
225
+ # a String or Symbol representation of a valid state.
226
+ def sanitise_state(value)
227
+ if value.nil? || (value.is_a?(String) && value.empty?)
228
+ raise ArgumentError, _('State not specified.')
229
+ end
230
+
231
+ value = value.to_sym if value.is_a?(String)
232
+ unless value.is_a?(Symbol)
233
+ raise ArgumentError, _('State must be a Symbol, not %{type}') % { type: value.class }
234
+ end
235
+
236
+ valid_states = [:passed, :error, :failure, :skipped]
237
+ unless valid_states.include?(value)
238
+ raise ArgumentError, _('Invalid state %{state}. Valid states are: %{valid}.') % {
239
+ state: value.inspect,
240
+ valid: valid_states.map(&:inspect).join(', '),
241
+ }
242
+ end
243
+
244
+ value
245
+ end
246
+
247
+ # Validates the source of the event.
248
+ #
249
+ # @param value [String, Symbol] The name of the source of the event.
250
+ #
251
+ # @return [String] the value passed to the event, converted to a String
252
+ # if necessary.
253
+ #
254
+ # @raise [ArgumentError] if the value is nil or an empty String.
255
+ def sanitise_source(value)
256
+ if value.nil? || (value.is_a?(String) && value.empty?)
257
+ raise ArgumentError, _('Source not specified.')
258
+ end
259
+
260
+ value.to_s
261
+ end
262
+
263
+ # Munges the line number of the event into an Integer.
264
+ #
265
+ # @param value [Integer, String, Fixnum] The line number.
266
+ #
267
+ # @return [Integer] the provided value, converted into an Integer if
268
+ # necessary.
269
+ def sanitise_line(value)
270
+ return nil if value.nil?
271
+
272
+ valid_types = [String, Integer]
273
+ if RUBY_VERSION.split('.')[0..1].join('.').to_f < 2.4
274
+ valid_types << Fixnum # rubocop:disable Lint/UnifiedInteger
275
+ end
276
+
277
+ unless valid_types.include?(value.class)
278
+ raise ArgumentError, _('Line must be an Integer or a String representation of an Integer.')
279
+ end
280
+
281
+ if value.is_a?(String) && value !~ %r{\A[0-9]+\Z}
282
+ raise ArgumentError, _('The line number can contain only the digits 0-9.')
283
+ end
284
+
285
+ value.to_i
286
+ end
287
+
288
+ # Munges the column number of the event into an Integer.
289
+ #
290
+ # @param value [Integer, String, Fixnum] The column number.
291
+ #
292
+ # @return [Integer] the provided value, converted into an Integer if
293
+ # necessary.
294
+ def sanitise_column(value)
295
+ return nil if value.nil?
296
+
297
+ valid_types = [String, Integer]
298
+ if RUBY_VERSION.split('.')[0..1].join('.').to_f < 2.4
299
+ valid_types << Fixnum # rubocop:disable Lint/UnifiedInteger
300
+ end
301
+
302
+ unless valid_types.include?(value.class)
303
+ raise ArgumentError, _('Column must be an Integer or a String representation of an Integer.')
304
+ end
305
+
306
+ if value.is_a?(String) && value !~ %r{\A[0-9]+\Z}
307
+ raise ArgumentError, _('The column number can contain only the digits 0-9.')
308
+ end
309
+
310
+ value.to_i
311
+ end
312
+
313
+ # Cleans up provided stack trace by removing entries that are inside gems
314
+ # or the rspec binstub.
315
+ #
316
+ # @param value [Array] Array of stack trace lines
317
+ #
318
+ # @return [Array] Array of stack trace lines with less relevant lines excluded
319
+ def sanitise_trace(value)
320
+ return nil if value.nil?
321
+
322
+ valid_types = [Array]
323
+
324
+ unless valid_types.include?(value.class)
325
+ raise ArgumentError, _('Trace must be an Array of stack trace lines.')
326
+ end
327
+
328
+ # Drop any stacktrace lines that include '/gems/' in the path or
329
+ # are the original rspec binstub lines
330
+ value.reject do |line|
331
+ (line =~ %r{/gems/}) || (line =~ %r{bin/rspec:})
332
+ end
333
+ end
334
+
335
+ # Extract contextual information for the event from the file that it
336
+ # references.
337
+ #
338
+ # @param max_num_lines [Integer] The maximum number of lines to return.
339
+ #
340
+ # @return [Array] Array of lines from the file, centred on the line
341
+ # number of the event.
342
+ def context_lines(max_num_lines = 5)
343
+ return if file.nil? || line.nil?
344
+
345
+ file_path = [file, File.join(PDK::Util.module_root, file)].find { |r| File.file?(r) }
346
+ return if file_path.nil?
347
+
348
+ file_content = File.read(file_path).split("\n")
349
+ delta = (max_num_lines - 1) / 2
350
+ min = [0, (line - 1) - delta].max
351
+ max = [(line - 1) + delta, file_content.length].min
352
+
353
+ file_content[min..max].map { |r| " #{r}" }
354
+ end
355
+ end
356
+ end
357
+ end