pdk-akerl 1.8.0.1

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