pdk 1.9.0 → 3.2.0

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 (163) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +744 -711
  3. data/README.md +23 -21
  4. data/lib/pdk/answer_file.rb +3 -112
  5. data/lib/pdk/bolt.rb +20 -0
  6. data/lib/pdk/cli/build.rb +51 -54
  7. data/lib/pdk/cli/bundle.rb +33 -29
  8. data/lib/pdk/cli/console.rb +148 -0
  9. data/lib/pdk/cli/convert.rb +46 -37
  10. data/lib/pdk/cli/env.rb +51 -0
  11. data/lib/pdk/cli/errors.rb +4 -3
  12. data/lib/pdk/cli/exec/command.rb +285 -0
  13. data/lib/pdk/cli/exec/interactive_command.rb +109 -0
  14. data/lib/pdk/cli/exec.rb +32 -201
  15. data/lib/pdk/cli/exec_group.rb +79 -43
  16. data/lib/pdk/cli/get/config.rb +26 -0
  17. data/lib/pdk/cli/get.rb +22 -0
  18. data/lib/pdk/cli/new/class.rb +20 -22
  19. data/lib/pdk/cli/new/defined_type.rb +21 -21
  20. data/lib/pdk/cli/new/fact.rb +27 -0
  21. data/lib/pdk/cli/new/function.rb +27 -0
  22. data/lib/pdk/cli/new/module.rb +40 -29
  23. data/lib/pdk/cli/new/provider.rb +18 -18
  24. data/lib/pdk/cli/new/task.rb +23 -22
  25. data/lib/pdk/cli/new/test.rb +52 -0
  26. data/lib/pdk/cli/new/transport.rb +27 -0
  27. data/lib/pdk/cli/new.rb +15 -9
  28. data/lib/pdk/cli/release/prep.rb +39 -0
  29. data/lib/pdk/cli/release/publish.rb +46 -0
  30. data/lib/pdk/cli/release.rb +185 -0
  31. data/lib/pdk/cli/remove/config.rb +83 -0
  32. data/lib/pdk/cli/remove.rb +22 -0
  33. data/lib/pdk/cli/set/config.rb +121 -0
  34. data/lib/pdk/cli/set.rb +22 -0
  35. data/lib/pdk/cli/test/unit.rb +71 -69
  36. data/lib/pdk/cli/test.rb +9 -8
  37. data/lib/pdk/cli/update.rb +38 -21
  38. data/lib/pdk/cli/util/command_redirector.rb +13 -3
  39. data/lib/pdk/cli/util/interview.rb +25 -9
  40. data/lib/pdk/cli/util/option_normalizer.rb +6 -6
  41. data/lib/pdk/cli/util/option_validator.rb +19 -9
  42. data/lib/pdk/cli/util/spinner.rb +13 -0
  43. data/lib/pdk/cli/util/update_manager_printer.rb +82 -0
  44. data/lib/pdk/cli/util.rb +105 -48
  45. data/lib/pdk/cli/validate.rb +96 -111
  46. data/lib/pdk/cli.rb +134 -87
  47. data/lib/pdk/config/errors.rb +5 -0
  48. data/lib/pdk/config/ini_file.rb +184 -0
  49. data/lib/pdk/config/ini_file_setting.rb +35 -0
  50. data/lib/pdk/config/json.rb +35 -0
  51. data/lib/pdk/config/json_schema_namespace.rb +137 -0
  52. data/lib/pdk/config/json_schema_setting.rb +51 -0
  53. data/lib/pdk/config/json_with_schema.rb +47 -0
  54. data/lib/pdk/config/namespace.rb +362 -0
  55. data/lib/pdk/config/setting.rb +134 -0
  56. data/lib/pdk/config/task_schema.json +116 -0
  57. data/lib/pdk/config/validator.rb +31 -0
  58. data/lib/pdk/config/yaml.rb +41 -0
  59. data/lib/pdk/config/yaml_with_schema.rb +51 -0
  60. data/lib/pdk/config.rb +304 -0
  61. data/lib/pdk/context/control_repo.rb +61 -0
  62. data/lib/pdk/context/module.rb +28 -0
  63. data/lib/pdk/context/none.rb +22 -0
  64. data/lib/pdk/context.rb +98 -0
  65. data/lib/pdk/control_repo.rb +89 -0
  66. data/lib/pdk/generate/defined_type.rb +27 -33
  67. data/lib/pdk/generate/fact.rb +26 -0
  68. data/lib/pdk/generate/function.rb +49 -0
  69. data/lib/pdk/generate/module.rb +160 -153
  70. data/lib/pdk/generate/provider.rb +16 -69
  71. data/lib/pdk/generate/puppet_class.rb +27 -32
  72. data/lib/pdk/generate/puppet_object.rb +100 -159
  73. data/lib/pdk/generate/task.rb +34 -51
  74. data/lib/pdk/generate/transport.rb +34 -0
  75. data/lib/pdk/generate.rb +21 -8
  76. data/lib/pdk/logger.rb +24 -6
  77. data/lib/pdk/module/build.rb +125 -37
  78. data/lib/pdk/module/convert.rb +146 -65
  79. data/lib/pdk/module/metadata.rb +72 -71
  80. data/lib/pdk/module/release.rb +255 -0
  81. data/lib/pdk/module/update.rb +48 -37
  82. data/lib/pdk/module/update_manager.rb +75 -39
  83. data/lib/pdk/module.rb +10 -2
  84. data/lib/pdk/monkey_patches.rb +268 -0
  85. data/lib/pdk/report/event.rb +36 -48
  86. data/lib/pdk/report.rb +35 -22
  87. data/lib/pdk/template/fetcher/git.rb +84 -0
  88. data/lib/pdk/template/fetcher/local.rb +29 -0
  89. data/lib/pdk/template/fetcher.rb +100 -0
  90. data/lib/pdk/template/renderer/v1/legacy_template_dir.rb +108 -0
  91. data/lib/pdk/template/renderer/v1/renderer.rb +131 -0
  92. data/lib/pdk/template/renderer/v1/template_file.rb +100 -0
  93. data/lib/pdk/template/renderer/v1.rb +25 -0
  94. data/lib/pdk/template/renderer.rb +97 -0
  95. data/lib/pdk/template/template_dir.rb +67 -0
  96. data/lib/pdk/template.rb +52 -0
  97. data/lib/pdk/tests/unit.rb +101 -51
  98. data/lib/pdk/util/bundler.rb +44 -42
  99. data/lib/pdk/util/changelog_generator.rb +138 -0
  100. data/lib/pdk/util/env.rb +48 -0
  101. data/lib/pdk/util/filesystem.rb +139 -2
  102. data/lib/pdk/util/git.rb +108 -8
  103. data/lib/pdk/util/json_finder.rb +86 -0
  104. data/lib/pdk/util/puppet_strings.rb +125 -0
  105. data/lib/pdk/util/puppet_version.rb +71 -87
  106. data/lib/pdk/util/ruby_version.rb +49 -25
  107. data/lib/pdk/util/template_uri.rb +283 -0
  108. data/lib/pdk/util/vendored_file.rb +34 -42
  109. data/lib/pdk/util/version.rb +11 -10
  110. data/lib/pdk/util/windows/api_types.rb +74 -44
  111. data/lib/pdk/util/windows/file.rb +31 -27
  112. data/lib/pdk/util/windows/process.rb +74 -0
  113. data/lib/pdk/util/windows/string.rb +19 -12
  114. data/lib/pdk/util/windows.rb +2 -0
  115. data/lib/pdk/util.rb +111 -124
  116. data/lib/pdk/validate/control_repo/control_repo_validator_group.rb +23 -0
  117. data/lib/pdk/validate/control_repo/environment_conf_validator.rb +98 -0
  118. data/lib/pdk/validate/external_command_validator.rb +213 -0
  119. data/lib/pdk/validate/internal_ruby_validator.rb +101 -0
  120. data/lib/pdk/validate/invokable_validator.rb +238 -0
  121. data/lib/pdk/validate/metadata/metadata_json_lint_validator.rb +84 -0
  122. data/lib/pdk/validate/metadata/metadata_syntax_validator.rb +76 -0
  123. data/lib/pdk/validate/metadata/metadata_validator_group.rb +20 -0
  124. data/lib/pdk/validate/puppet/puppet_epp_validator.rb +131 -0
  125. data/lib/pdk/validate/puppet/puppet_lint_validator.rb +66 -0
  126. data/lib/pdk/validate/puppet/puppet_plan_syntax_validator.rb +38 -0
  127. data/lib/pdk/validate/puppet/puppet_syntax_validator.rb +135 -0
  128. data/lib/pdk/validate/puppet/puppet_validator_group.rb +22 -0
  129. data/lib/pdk/validate/ruby/ruby_rubocop_validator.rb +79 -0
  130. data/lib/pdk/validate/ruby/ruby_validator_group.rb +19 -0
  131. data/lib/pdk/validate/tasks/tasks_metadata_lint_validator.rb +83 -0
  132. data/lib/pdk/validate/tasks/tasks_name_validator.rb +45 -0
  133. data/lib/pdk/validate/tasks/tasks_validator_group.rb +20 -0
  134. data/lib/pdk/validate/validator.rb +120 -0
  135. data/lib/pdk/validate/validator_group.rb +107 -0
  136. data/lib/pdk/validate/yaml/yaml_syntax_validator.rb +86 -0
  137. data/lib/pdk/validate/yaml/yaml_validator_group.rb +19 -0
  138. data/lib/pdk/validate.rb +86 -12
  139. data/lib/pdk/version.rb +2 -2
  140. data/lib/pdk.rb +60 -10
  141. metadata +138 -100
  142. data/lib/pdk/cli/module/build.rb +0 -14
  143. data/lib/pdk/cli/module/generate.rb +0 -45
  144. data/lib/pdk/cli/module.rb +0 -14
  145. data/lib/pdk/i18n.rb +0 -4
  146. data/lib/pdk/module/templatedir.rb +0 -321
  147. data/lib/pdk/template_file.rb +0 -95
  148. data/lib/pdk/validate/base_validator.rb +0 -215
  149. data/lib/pdk/validate/metadata/metadata_json_lint.rb +0 -86
  150. data/lib/pdk/validate/metadata/metadata_syntax.rb +0 -109
  151. data/lib/pdk/validate/metadata_validator.rb +0 -30
  152. data/lib/pdk/validate/puppet/puppet_lint.rb +0 -67
  153. data/lib/pdk/validate/puppet/puppet_syntax.rb +0 -112
  154. data/lib/pdk/validate/puppet_validator.rb +0 -30
  155. data/lib/pdk/validate/ruby/rubocop.rb +0 -77
  156. data/lib/pdk/validate/ruby_validator.rb +0 -29
  157. data/lib/pdk/validate/tasks/metadata_lint.rb +0 -126
  158. data/lib/pdk/validate/tasks/name.rb +0 -88
  159. data/lib/pdk/validate/tasks_validator.rb +0 -33
  160. data/lib/pdk/validate/yaml/syntax.rb +0 -109
  161. data/lib/pdk/validate/yaml_validator.rb +0 -31
  162. data/locales/config.yaml +0 -21
  163. data/locales/pdk.pot +0 -1291
@@ -0,0 +1,213 @@
1
+ require 'pdk'
2
+
3
+ module PDK
4
+ module Validate
5
+ # An abstract validator that runs external commands within a Ruby Bundled environment
6
+ # e.g. `puppet-lint`, or `puppet validate`
7
+ #
8
+ # At a a minimum child classes should implment the `name`, `cmd`, `pattern` and `parse_output` methods
9
+ #
10
+ # An example concrete implementation could look like:
11
+ #
12
+ # module PDK
13
+ # module Validate
14
+ # module Ruby
15
+ # class RubyRubocopValidator < ExternalCommandValidator
16
+ # def name
17
+ # 'rubocop'
18
+ # end
19
+ #
20
+ # def cmd
21
+ # 'rubocop'
22
+ # end
23
+ #
24
+ # def pattern
25
+ # '**/**.rb'
26
+ # end
27
+ #
28
+ # def parse_options(targets)
29
+ # ['--format', 'json']
30
+ # end
31
+ #
32
+ # def parse_output(report, result, _targets)
33
+ # ... ruby code ...
34
+ # report.add_event(
35
+ # line: offense['location']['line'],
36
+ # column: offense['location']['column'],
37
+ # message: offense['message'],
38
+ # severity: offense['corrected'] ? 'corrected' : offense['severity'],
39
+ # test: offense['cop_name'],
40
+ # state: :failure,
41
+ # )
42
+ # end
43
+ # end
44
+ # end
45
+ # end
46
+ # end
47
+ #
48
+ # @see PDK::Validate::InvokableValidator
49
+ class ExternalCommandValidator < InvokableValidator
50
+ # @return Array[PDK::CLI::Exec::Command] This is a private implementation attribute used for unit testing
51
+ # @api private
52
+ attr_reader :commands
53
+
54
+ # @see PDK::Validate::Validator.spinner
55
+ def spinner
56
+ # The validator has sub-commands with their own spinners.
57
+ nil
58
+ end
59
+
60
+ # Calculates the text of the spinner based on the target list
61
+ # @return [String]
62
+ # @abstract
63
+ def spinner_text_for_targets(targets); end
64
+
65
+ # The name of the command to be run for validation
66
+ # @return [String]
67
+ # @abstract
68
+ def cmd; end
69
+
70
+ # Alternate paths which the command (cmd) may exist in. Typically other ruby gem caches,
71
+ # or packaged installation bin directories.
72
+ # @return [Array[String]]
73
+ # @api private
74
+ def alternate_bin_paths
75
+ [
76
+ PDK::Util::RubyVersion.bin_path,
77
+ File.join(PDK::Util::RubyVersion.gem_home, 'bin'),
78
+ PDK::Util::RubyVersion.gem_paths_raw.map { |gem_path_raw| File.join(gem_path_raw, 'bin') },
79
+ PDK::Util.package_install? ? File.join(PDK::Util.pdk_package_basedir, 'bin') : nil
80
+ ].flatten.compact
81
+ end
82
+
83
+ # The full path to the command (cmd)
84
+ # Can be overridden in child classes to a non-default path
85
+ # @return [String]
86
+ # @api private
87
+ def cmd_path
88
+ return @cmd_path unless @cmd_path.nil?
89
+
90
+ @cmd_path = File.join(context.root_path, 'bin', cmd)
91
+ # Return the path to the command if it exists on disk, or we have a gemfile (i.e. Bundled install)
92
+ # The Bundle may be created after the prepare_invoke so if the file doesn't exist, it may not be an error
93
+ return @cmd_path if PDK::Util::Filesystem.exist?(@cmd_path) || !PDK::Util::Bundler::BundleHelper.new.gemfile.nil?
94
+
95
+ # But if there is no Gemfile AND cmd doesn't exist in the default path, we need to go searching...
96
+ @cmd_path = alternate_bin_paths.map { |alternate_path| File.join(alternate_path, cmd) }
97
+ .find { |path| PDK::Util::Filesystem.exist?(path) }
98
+ return @cmd_path unless @cmd_path.nil?
99
+
100
+ # If we can't find it anywhere, just let the OS find it
101
+ @cmd_path = cmd
102
+ end
103
+
104
+ # An array of command line arguments to pass to the command for validation
105
+ # @return Array[String]
106
+ # @abstract
107
+ def parse_options(_targets)
108
+ []
109
+ end
110
+
111
+ # Parses the output from the command and appends formatted events to the report.
112
+ # This is called for each command, which is a group of targets
113
+ #
114
+ # @param report [PDK::Report] The report to add events to
115
+ # @param result [Hash[Symbol => Object]] The result of validation command process
116
+ # @param targets [Array[String]] The targets for this command result
117
+ # @api private
118
+ # @see PDK::CLI::Exec::Command.execute!
119
+ # @abstract
120
+ def parse_output(_report, _result, _targets); end
121
+
122
+ # Prepares for invokation by parsing targets and creating the needed commands.
123
+ # @api private
124
+ # @see PDK::Validate::Validator.prepare_invoke!
125
+ def prepare_invoke!
126
+ return if @prepared
127
+
128
+ super
129
+
130
+ @targets, @skipped, @invalid = parse_targets
131
+ @targets = [] if @targets.nil?
132
+
133
+ target_groups = if @targets.empty? && allow_empty_targets?
134
+ # If we have no targets and we allow empty targets, create an empty target group list
135
+ [[]]
136
+ elsif invoke_style == :per_target
137
+ # If invoking :per_target, split the targets array into an array of
138
+ # single element arrays (one per target).
139
+ @targets.combination(1).to_a.compact
140
+ else
141
+ # Else we're invoking :once, wrap the targets array in another array. This is so we
142
+ # can loop through the invokes with the same logic, regardless of which invoke style
143
+ # is needed.
144
+ @targets.each_slice(1000).to_a.compact
145
+ end
146
+
147
+ # Register all of the commands for all of the targets
148
+ @commands = []
149
+ target_groups.each do |invokation_targets|
150
+ next if invokation_targets.empty? && !allow_empty_targets?
151
+
152
+ cmd_argv = parse_options(invokation_targets).unshift(cmd_path).compact
153
+ cmd_argv.unshift(File.join(PDK::Util::RubyVersion.bin_path, 'ruby.exe'), '-W0') if Gem.win_platform?
154
+
155
+ command = PDK::CLI::Exec::Command.new(*cmd_argv).tap do |c|
156
+ c.context = :module
157
+ c.environment = { 'PUPPET_GEM_VERSION' => options[:puppet] } if options[:puppet]
158
+
159
+ if spinners_enabled?
160
+ parent_validator = options[:parent_validator]
161
+ if parent_validator.nil? || parent_validator.spinner.nil? || !parent_validator.spinner.is_a?(TTY::Spinner::Multi)
162
+ c.add_spinner(spinner_text_for_targets(invokation_targets))
163
+ else
164
+ spinner = TTY::Spinner.new("[:spinner] #{spinner_text_for_targets(invokation_targets)}", PDK::CLI::Util.spinner_opts_for_platform)
165
+ parent_validator.spinner.register(spinner)
166
+ c.register_spinner(spinner, PDK::CLI::Util.spinner_opts_for_platform)
167
+ end
168
+ end
169
+ end
170
+
171
+ @commands << { command: command, invokation_targets: invokation_targets }
172
+ end
173
+ nil
174
+ end
175
+
176
+ # Invokes the prepared commands as an ExecGroup
177
+ # @see PDK::Validate::Validator.invoke
178
+ def invoke(report)
179
+ prepare_invoke!
180
+
181
+ process_skipped(report, @skipped)
182
+ process_invalid(report, @invalid)
183
+
184
+ # Nothing to execute so return success
185
+ return 0 if @commands.empty?
186
+
187
+ # If there's no Gemfile, then we can't ensure the binstubs are correct
188
+ PDK::Util::Bundler.ensure_binstubs!(cmd) unless PDK::Util::Bundler::BundleHelper.new.gemfile.nil?
189
+
190
+ exec_group = PDK::CLI::ExecGroup.create(name, { parallel: false }, options)
191
+
192
+ # Register all of the commands for all of the targets
193
+ @commands.each do |item|
194
+ command = item[:command]
195
+ invokation_targets = item[:invokation_targets]
196
+
197
+ exec_group.register do
198
+ result = command.execute!
199
+ begin
200
+ parse_output(report, result, invokation_targets.compact)
201
+ rescue PDK::Validate::ParseOutputError => e
202
+ $stderr.puts e.message
203
+ end
204
+ result[:exit_code]
205
+ end
206
+ end
207
+
208
+ # Now execute and get the return code
209
+ exec_group.exit_code
210
+ end
211
+ end
212
+ end
213
+ end
@@ -0,0 +1,101 @@
1
+ require 'pdk'
2
+
3
+ module PDK
4
+ module Validate
5
+ # An abstract validator that runs ruby code internal to the PDK e.g. JSON and YAML validation, on a single file.
6
+ # The validator code must run within the PDK Ruby environment as opposed to the bundled Ruby environment for a module.
7
+ #
8
+ # At a a minimum child classes should implment the `name`, `pattern` and `validate_target` methods
9
+ #
10
+ # An example concrete implementation could look like:
11
+ #
12
+ # module PDK
13
+ # module Validate
14
+ # module Tasks
15
+ # class TasksNameValidator < InternalRubyValidator
16
+ # def name
17
+ # 'task-name'
18
+ # end
19
+ #
20
+ # def pattern
21
+ # 'tasks/**/*'
22
+ # end
23
+ #
24
+ # def validate_target(report, target)
25
+ # task_name = File.basename(target, File.extname(target))
26
+ # ... ruby code ...
27
+ # success ? 0 : 1
28
+ # end
29
+ # end
30
+ # end
31
+ # end
32
+ # end
33
+ #
34
+ #
35
+ # @abstract
36
+ # @see PDK::Validate::InvokableValidator
37
+ class InternalRubyValidator < InvokableValidator
38
+ # @see PDK::Validate::Validator.prepare_invoke!
39
+ def prepare_invoke!
40
+ return if @prepared
41
+
42
+ super
43
+
44
+ # Parse the targets
45
+ @targets, @skipped, @invalid = parse_targets
46
+
47
+ nil
48
+ end
49
+
50
+ # Invokes the validator to call `validate_target` on each target
51
+ # @see PDK::Validate::Validator.invoke
52
+ def invoke(report)
53
+ prepare_invoke!
54
+
55
+ process_skipped(report, @skipped)
56
+ process_invalid(report, @invalid)
57
+
58
+ return 0 if @targets.empty?
59
+
60
+ return_val = 0
61
+
62
+ before_validation
63
+
64
+ start_spinner
65
+ @targets.each do |target|
66
+ validation_result = validate_target(report, target)
67
+ if validation_result.nil?
68
+ report.add_event(
69
+ file: target,
70
+ source: name,
71
+ state: :failure,
72
+ severity: 'error',
73
+ message: "Validation did not return an exit code for #{target}"
74
+ )
75
+ validation_result = 1
76
+ end
77
+ return_val = validation_result if validation_result > return_val
78
+ end
79
+
80
+ stop_spinner(return_val.zero?)
81
+ return_val
82
+ end
83
+
84
+ # Validates a single target
85
+ # It is the responsibility of this method to populate the report with validation messages
86
+ #
87
+ # @param report [PDK::Report] The report to add the events to
88
+ # @param target [String] The target to validate
89
+ #
90
+ # @return [Integer] The exitcode of the validation. Zero indicates success. A non-zero code indicates failure
91
+ # @api private
92
+ # @abstract
93
+ def validate_target(report, target); end
94
+
95
+ # Tasks to run before validation occurs. This is run once every time `.invoke` is called
96
+ # @api private
97
+ # @abstract
98
+ def before_validation; end
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,238 @@
1
+ require 'pdk'
2
+
3
+ module PDK
4
+ module Validate
5
+ # A base class for file based validators.
6
+ # This class provides base methods and helpers to help determine the file targets to validate against.
7
+ # Acutal validator implementation should inherit from other child abstract classes e.g. ExternalCommandValdiator
8
+ # @see PDK::Validate::Validator
9
+ # @abstract
10
+ class InvokableValidator < Validator
11
+ # Controls how many times the validator is invoked.
12
+ #
13
+ # :once - The validator will be invoked once and passed all the
14
+ # targets.
15
+ # :per_target - The validator will be invoked for each target
16
+ # separately.
17
+ # @abstract
18
+ def invoke_style
19
+ :once
20
+ end
21
+
22
+ # Whether this Validator can be invoked in this context. By default any InvokableValidator can work in any Context, except ::None
23
+ # @see PDK::Validate::Validator
24
+ def valid_in_context?
25
+ !context.is_a?(PDK::Context::None)
26
+ end
27
+
28
+ # An array, or a string, of glob patterns to use to find targets
29
+ # @return [Array[String], String]
30
+ # @abstract
31
+ def pattern; end
32
+
33
+ # An array, or a string, of glob patterns to use to ignore targets
34
+ # @return [Array[String], String, Nil]
35
+ # @abstract
36
+ def pattern_ignore; end
37
+
38
+ # @see PDK::Validate::Validator.prepare_invoke!
39
+ def prepare_invoke!
40
+ return if @prepared
41
+
42
+ super
43
+
44
+ # Register the spinner
45
+ spinner
46
+ nil
47
+ end
48
+
49
+ # Parses the target strings provided from the CLI
50
+ #
51
+ # @param options [Hash] A Hash containing the input options from the CLI.
52
+ #
53
+ # @return targets [Array] An Array of Strings containing target file paths
54
+ # for the validator to validate.
55
+ # @return skipped [Array] An Array of Strings containing targets
56
+ # that are skipped due to target not containing
57
+ # any files that can be validated by the validator.
58
+ # @return invalid [Array] An Array of Strings containing targets that do
59
+ # not exist, and will not be run by validator.
60
+ def parse_targets
61
+ requested_targets = options.fetch(:targets, [])
62
+ # If no targets are specified and empty targets are allowed return with an empty list.
63
+ # It will be up to the validator (and whatever validation tool it uses) to determine the
64
+ # targets. For example, using rubocop with no targets, will allow rubocop to determine the
65
+ # target list using it's .rubocop.yml file
66
+ return [[], [], []] if requested_targets.empty? && allow_empty_targets?
67
+
68
+ # If no targets are specified, then we will run validations from the base context directory.
69
+ targets = requested_targets.empty? ? [context.root_path] : requested_targets
70
+ targets.map! { |r| r.gsub(File::ALT_SEPARATOR, File::SEPARATOR) } if File::ALT_SEPARATOR
71
+
72
+ # If this validator is not valid in this context then skip all of the targets
73
+ return [[], targets, []] unless valid_in_context?
74
+
75
+ skipped = []
76
+ invalid = []
77
+ matched = targets.filter_map do |target|
78
+ if pattern.nil?
79
+ target
80
+ else
81
+ if PDK::Util::Filesystem.directory?(target) # rubocop:disable Style/IfInsideElse
82
+ target_root = context.root_path
83
+ pattern_glob = Array(pattern).map { |p| PDK::Util::Filesystem.glob(File.join(target_root, p), File::FNM_DOTMATCH) }
84
+ target_list = pattern_glob.flatten
85
+ .select { |glob| PDK::Util::Filesystem.fnmatch(File.join(PDK::Util::Filesystem.expand_path(PDK::Util.canonical_path(target)), '*'), glob, File::FNM_DOTMATCH) }
86
+ .map { |glob| Pathname.new(glob).relative_path_from(Pathname.new(context.root_path)).to_s }
87
+
88
+ ignore_list = ignore_pathspec
89
+ target_list = target_list.reject { |file| ignore_list.match(file) }
90
+
91
+ if target_list.flatten.empty?
92
+ PDK.logger.info(format('Validator \'%{validator}\' skipped for \'%{target}\'. No files matching \'%{pattern}\' found to validate.', validator: name, target: target, pattern: pattern))
93
+
94
+ skipped << target
95
+ end
96
+
97
+ target_list
98
+ elsif PDK::Util::Filesystem.file?(target)
99
+ if (Array(pattern).include? target) || fnmatch?(pattern, target)
100
+ target
101
+ else
102
+ skipped << target
103
+ next
104
+ end
105
+ else
106
+ invalid << target
107
+ next
108
+ end
109
+ end
110
+ end.flatten.uniq
111
+ [matched, skipped, invalid]
112
+ end
113
+
114
+ # Matches a target against a pattern
115
+ # @param pattern [String, Array[String]] The pattern to match against
116
+ # @return [Boolean]
117
+ def fnmatch?(pattern, target)
118
+ Array(pattern).any? { |p| PDK::Util::Filesystem.fnmatch(PDK::Util::Filesystem.expand_path(p), PDK::Util::Filesystem.expand_path(target), File::FNM_DOTMATCH) }
119
+ end
120
+
121
+ # Whether the target parsing ignores "dotfiles" (e.g. .gitignore or .pdkignore) which are considered hidden files in POSIX
122
+ # @return [Boolean]
123
+ # @abstract
124
+ def ignore_dotfiles?
125
+ true
126
+ end
127
+
128
+ # @see PDK::Validate::Validator.spinner_text
129
+ # @abstract
130
+ def spinner_text
131
+ format('Running %{name} validator ...', name: name)
132
+ end
133
+
134
+ # @see PDK::Validate::Validator.spinner
135
+ def spinner
136
+ return nil unless spinners_enabled?
137
+ return @spinner unless @spinner.nil?
138
+
139
+ require 'pdk/cli/util/spinner'
140
+
141
+ @spinner = TTY::Spinner.new("[:spinner] #{spinner_text}", PDK::CLI::Util.spinner_opts_for_platform)
142
+ end
143
+
144
+ # Process any targets that were skipped by the validator and add the events to the validation report
145
+ # @param report [PDK::Report] The report to add the events to
146
+ # @param skipped [Array[String]] The list of skipped targets
147
+ def process_skipped(report, skipped = [])
148
+ skipped.each do |skipped_target|
149
+ PDK.logger.debug(format('%{validator}: Skipped \'%{target}\'. Target does not contain any files to validate (%{pattern}).', validator: name, target: skipped_target, pattern: pattern))
150
+ report.add_event(
151
+ file: skipped_target,
152
+ source: name,
153
+ message: format('Target does not contain any files to validate (%{pattern}).', pattern: pattern),
154
+ severity: :info,
155
+ state: :skipped
156
+ )
157
+ end
158
+ end
159
+
160
+ # Process any targets that were invalid by the validator and add the events to the validation report
161
+ # @param report [PDK::Report] The report to add the events to
162
+ # @param invalid [Array[String]] The list of invalid targets
163
+ def process_invalid(report, invalid = [])
164
+ invalid.each do |invalid_target|
165
+ PDK.logger.debug(format('%{validator}: Skipped \'%{target}\'. Target file not found.', validator: name, target: invalid_target))
166
+ report.add_event(
167
+ file: invalid_target,
168
+ source: name,
169
+ message: 'File does not exist.',
170
+ severity: :error,
171
+ state: :error
172
+ )
173
+ end
174
+ end
175
+
176
+ # Controls how the validator behaves if not passed any targets.
177
+ #
178
+ # true - PDK will not pass the globbed targets to the validator command
179
+ # and it will instead rely on the underlying tool to find its
180
+ # own default targets.
181
+ # false - PDK will pass the globbed targets to the validator command.
182
+ # @abstract
183
+ def allow_empty_targets?
184
+ false
185
+ end
186
+
187
+ protected
188
+
189
+ # Takes the pattern used in a module context and transforms it depending on the
190
+ # context e.g. A Control Repo will use the module pattern in each module path
191
+ #
192
+ # @param [Array, String] The pattern when used in the module root. Does not start with '/'
193
+ #
194
+ # @return [Array[String]]
195
+ def contextual_pattern(module_pattern)
196
+ module_pattern = [module_pattern] unless module_pattern.is_a?(Array)
197
+ return module_pattern unless context.is_a?(PDK::Context::ControlRepo)
198
+
199
+ context.actualized_module_paths.map { |mod_path| module_pattern.map { |pat_path| "#{mod_path}/*/#{pat_path}" } }.flatten
200
+ end
201
+
202
+ private
203
+
204
+ # Helper method to collate the default ignored paths
205
+ # @return [PathSpec] Paths to ignore
206
+ def ignore_pathspec
207
+ ignore_pathspec = case context
208
+ when PDK::Context::Module
209
+ require 'pdk/module'
210
+ PDK::Module.default_ignored_pathspec(ignore_dotfiles?)
211
+ when PDK::Context::ControlRepo
212
+ require 'pdk/control_repo'
213
+ PDK::ControlRepo.default_ignored_pathspec(ignore_dotfiles?)
214
+ else
215
+ PathSpec.new.tap do |ps|
216
+ ps.add('.*') if ignore_dotfiles?
217
+ end
218
+ end
219
+
220
+ unless pattern_ignore.nil?
221
+ Array(pattern_ignore).each do |pattern|
222
+ ignore_pathspec.add(pattern)
223
+ end
224
+ end
225
+
226
+ # block will always be [] because it is intialized in config
227
+ ignore_files = PDK.config.get_within_scopes('validate.ignore')
228
+ unless ignore_files.nil? || ignore_files.empty?
229
+ Array(ignore_files).each do |pattern|
230
+ ignore_pathspec.add(pattern)
231
+ end
232
+ end
233
+
234
+ ignore_pathspec
235
+ end
236
+ end
237
+ end
238
+ end
@@ -0,0 +1,84 @@
1
+ require 'pdk'
2
+
3
+ module PDK
4
+ module Validate
5
+ module Metadata
6
+ class MetadataJSONLintValidator < ExternalCommandValidator
7
+ # Validate each metadata file separately, as metadata-json-lint does not
8
+ # support multiple targets.
9
+ def invoke_style
10
+ :per_target
11
+ end
12
+
13
+ def name
14
+ 'metadata-json-lint'
15
+ end
16
+
17
+ def cmd
18
+ 'metadata-json-lint'
19
+ end
20
+
21
+ def spinner_text_for_targets(targets)
22
+ format('Checking module metadata style (%{targets}).', targets: PDK::Util.targets_relative_to_pwd(targets.flatten).join(' '))
23
+ end
24
+
25
+ def pattern
26
+ contextual_pattern('metadata.json')
27
+ end
28
+
29
+ def parse_options(targets)
30
+ cmd_options = ['--format', 'json']
31
+ cmd_options << '--strict-dependencies'
32
+
33
+ cmd_options.concat(targets)
34
+ end
35
+
36
+ def parse_output(report, result, targets)
37
+ raise ArgumentError, 'More than 1 target provided to PDK::Validate::MetadataJSONLintValidator.' if targets.count > 1
38
+
39
+ if result[:stdout].strip.empty?
40
+ # metadata-json-lint will print nothing if there are no problems with
41
+ # the file being linted. This should be handled separately to
42
+ # metadata-json-lint generating output that can not be parsed as JSON
43
+ # (unhandled exception in metadata-json-lint).
44
+ json_data = {}
45
+ else
46
+ begin
47
+ json_data = JSON.parse(result[:stdout])
48
+ rescue JSON::ParserError
49
+ raise PDK::Validate::ParseOutputError, result[:stdout]
50
+ end
51
+ end
52
+
53
+ if json_data.empty?
54
+ report.add_event(
55
+ file: targets.first,
56
+ source: name,
57
+ state: :passed,
58
+ severity: :ok
59
+ )
60
+ else
61
+ json_data.delete('result')
62
+ json_data.each_key do |type|
63
+ json_data[type].each do |offense|
64
+ # metadata-json-lint groups the offenses by type, so the type ends
65
+ # up being `warnings` or `errors`. We want to convert that to the
66
+ # singular noun for the event.
67
+ event_type = type[/\A(.+?)s?\Z/, 1]
68
+
69
+ report.add_event(
70
+ file: targets.first,
71
+ source: name,
72
+ message: offense['msg'],
73
+ test: offense['check'],
74
+ severity: event_type,
75
+ state: :failure
76
+ )
77
+ end
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end