lib/pdk/validate/external_command_validator.rb in pdk-2.3.0 vs lib/pdk/validate/external_command_validator.rb in pdk-2.4.0
- old
+ new
@@ -1,208 +1,208 @@
-require 'pdk'
-
-module PDK
- module Validate
- # An abstract validator that runs external commands within a Ruby Bundled environment
- # e.g. `puppet-lint`, or `puppet validate`
- #
- # At a a minimum child classes should implment the `name`, `cmd`, `pattern` and `parse_output` methods
- #
- # An example concrete implementation could look like:
- #
- # module PDK
- # module Validate
- # module Ruby
- # class RubyRubocopValidator < ExternalCommandValidator
- # def name
- # 'rubocop'
- # end
- #
- # def cmd
- # 'rubocop'
- # end
- #
- # def pattern
- # '**/**.rb'
- # end
- #
- # def parse_options(targets)
- # ['--format', 'json']
- # end
- #
- # def parse_output(report, result, _targets)
- # ... ruby code ...
- # report.add_event(
- # line: offense['location']['line'],
- # column: offense['location']['column'],
- # message: offense['message'],
- # severity: offense['corrected'] ? 'corrected' : offense['severity'],
- # test: offense['cop_name'],
- # state: :failure,
- # )
- # end
- # end
- # end
- # end
- # end
- #
- # @see PDK::Validate::InvokableValidator
- class ExternalCommandValidator < InvokableValidator
- # @return Array[PDK::CLI::Exec::Command] This is a private implementation attribute used for unit testing
- # @api private
- attr_reader :commands
-
- # @see PDK::Validate::Validator.spinner
- def spinner
- # The validator has sub-commands with their own spinners.
- nil
- end
-
- # Calculates the text of the spinner based on the target list
- # @return [String]
- # @abstract
- def spinner_text_for_targets(targets); end
-
- # The name of the command to be run for validation
- # @return [String]
- # @abstract
- def cmd; end
-
- # Alternate paths which the command (cmd) may exist in. Typically other ruby gem caches,
- # or packaged installation bin directories.
- # @return [Array[String]]
- # @api private
- def alternate_bin_paths
- [
- PDK::Util::RubyVersion.bin_path,
- File.join(PDK::Util::RubyVersion.gem_home, 'bin'),
- PDK::Util::RubyVersion.gem_paths_raw.map { |gem_path_raw| File.join(gem_path_raw, 'bin') },
- PDK::Util.package_install? ? File.join(PDK::Util.pdk_package_basedir, 'bin') : nil,
- ].flatten.compact
- end
-
- # The full path to the command (cmd)
- # Can be overridden in child classes to a non-default path
- # @return [String]
- # @api private
- def cmd_path
- return @cmd_path unless @cmd_path.nil?
- @cmd_path = File.join(context.root_path, 'bin', cmd)
- # Return the path to the command if it exists on disk, or we have a gemfile (i.e. Bundled install)
- # The Bundle may be created after the prepare_invoke so if the file doesn't exist, it may not be an error
- return @cmd_path if PDK::Util::Filesystem.exist?(@cmd_path) || !PDK::Util::Bundler::BundleHelper.new.gemfile.nil?
- # But if there is no Gemfile AND cmd doesn't exist in the default path, we need to go searching...
- @cmd_path = alternate_bin_paths.map { |alternate_path| File.join(alternate_path, cmd) }
- .find { |path| PDK::Util::Filesystem.exist?(path) }
- return @cmd_path unless @cmd_path.nil?
- # If we can't find it anywhere, just let the OS find it
- @cmd_path = cmd
- end
-
- # An array of command line arguments to pass to the command for validation
- # @return Array[String]
- # @abstract
- def parse_options(_targets)
- []
- end
-
- # Parses the output from the command and appends formatted events to the report.
- # This is called for each command, which is a group of targets
- #
- # @param report [PDK::Report] The report to add events to
- # @param result [Hash[Symbol => Object]] The result of validation command process
- # @param targets [Array[String]] The targets for this command result
- # @api private
- # @see PDK::CLI::Exec::Command.execute!
- # @abstract
- def parse_output(_report, _result, _targets); end
-
- # Prepares for invokation by parsing targets and creating the needed commands.
- # @api private
- # @see PDK::Validate::Validator.prepare_invoke!
- def prepare_invoke!
- return if @prepared
- super
-
- @targets, @skipped, @invalid = parse_targets
- @targets = [] if @targets.nil?
-
- target_groups = if @targets.empty? && allow_empty_targets?
- # If we have no targets and we allow empty targets, create an empty target group list
- [[]]
- elsif invoke_style == :per_target
- # If invoking :per_target, split the targets array into an array of
- # single element arrays (one per target).
- @targets.combination(1).to_a.compact
- else
- # Else we're invoking :once, wrap the targets array in another array. This is so we
- # can loop through the invokes with the same logic, regardless of which invoke style
- # is needed.
- @targets.each_slice(1000).to_a.compact
- end
-
- # Register all of the commands for all of the targets
- @commands = []
- target_groups.each do |invokation_targets|
- next if invokation_targets.empty? && !allow_empty_targets?
- cmd_argv = parse_options(invokation_targets).unshift(cmd_path).compact
- cmd_argv.unshift(File.join(PDK::Util::RubyVersion.bin_path, 'ruby.exe'), '-W0') if Gem.win_platform?
-
- command = PDK::CLI::Exec::Command.new(*cmd_argv).tap do |c|
- c.context = :module
- c.environment = { 'PUPPET_GEM_VERSION' => options[:puppet] } if options[:puppet]
-
- if spinners_enabled?
- parent_validator = options[:parent_validator]
- if parent_validator.nil? || parent_validator.spinner.nil? || !parent_validator.spinner.is_a?(TTY::Spinner::Multi)
- c.add_spinner(spinner_text_for_targets(invokation_targets))
- else
- spinner = TTY::Spinner.new("[:spinner] #{spinner_text_for_targets(invokation_targets)}", PDK::CLI::Util.spinner_opts_for_platform)
- parent_validator.spinner.register(spinner)
- c.register_spinner(spinner, PDK::CLI::Util.spinner_opts_for_platform)
- end
- end
- end
-
- @commands << { command: command, invokation_targets: invokation_targets }
- end
- nil
- end
-
- # Invokes the prepared commands as an ExecGroup
- # @see PDK::Validate::Validator.invoke
- def invoke(report)
- prepare_invoke!
-
- process_skipped(report, @skipped)
- process_invalid(report, @invalid)
-
- # Nothing to execute so return success
- return 0 if @commands.empty?
-
- # If there's no Gemfile, then we can't ensure the binstubs are correct
- PDK::Util::Bundler.ensure_binstubs!(cmd) unless PDK::Util::Bundler::BundleHelper.new.gemfile.nil?
-
- exec_group = PDK::CLI::ExecGroup.create(name, { parallel: false }, options)
-
- # Register all of the commands for all of the targets
- @commands.each do |item|
- command = item[:command]
- invokation_targets = item[:invokation_targets]
-
- exec_group.register do
- result = command.execute!
- begin
- parse_output(report, result, invokation_targets.compact)
- rescue PDK::Validate::ParseOutputError => e
- $stderr.puts e.message
- end
- result[:exit_code]
- end
- end
-
- # Now execute and get the return code
- exec_group.exit_code
- end
- end
- end
-end
+require 'pdk'
+
+module PDK
+ module Validate
+ # An abstract validator that runs external commands within a Ruby Bundled environment
+ # e.g. `puppet-lint`, or `puppet validate`
+ #
+ # At a a minimum child classes should implment the `name`, `cmd`, `pattern` and `parse_output` methods
+ #
+ # An example concrete implementation could look like:
+ #
+ # module PDK
+ # module Validate
+ # module Ruby
+ # class RubyRubocopValidator < ExternalCommandValidator
+ # def name
+ # 'rubocop'
+ # end
+ #
+ # def cmd
+ # 'rubocop'
+ # end
+ #
+ # def pattern
+ # '**/**.rb'
+ # end
+ #
+ # def parse_options(targets)
+ # ['--format', 'json']
+ # end
+ #
+ # def parse_output(report, result, _targets)
+ # ... ruby code ...
+ # report.add_event(
+ # line: offense['location']['line'],
+ # column: offense['location']['column'],
+ # message: offense['message'],
+ # severity: offense['corrected'] ? 'corrected' : offense['severity'],
+ # test: offense['cop_name'],
+ # state: :failure,
+ # )
+ # end
+ # end
+ # end
+ # end
+ # end
+ #
+ # @see PDK::Validate::InvokableValidator
+ class ExternalCommandValidator < InvokableValidator
+ # @return Array[PDK::CLI::Exec::Command] This is a private implementation attribute used for unit testing
+ # @api private
+ attr_reader :commands
+
+ # @see PDK::Validate::Validator.spinner
+ def spinner
+ # The validator has sub-commands with their own spinners.
+ nil
+ end
+
+ # Calculates the text of the spinner based on the target list
+ # @return [String]
+ # @abstract
+ def spinner_text_for_targets(targets); end
+
+ # The name of the command to be run for validation
+ # @return [String]
+ # @abstract
+ def cmd; end
+
+ # Alternate paths which the command (cmd) may exist in. Typically other ruby gem caches,
+ # or packaged installation bin directories.
+ # @return [Array[String]]
+ # @api private
+ def alternate_bin_paths
+ [
+ PDK::Util::RubyVersion.bin_path,
+ File.join(PDK::Util::RubyVersion.gem_home, 'bin'),
+ PDK::Util::RubyVersion.gem_paths_raw.map { |gem_path_raw| File.join(gem_path_raw, 'bin') },
+ PDK::Util.package_install? ? File.join(PDK::Util.pdk_package_basedir, 'bin') : nil,
+ ].flatten.compact
+ end
+
+ # The full path to the command (cmd)
+ # Can be overridden in child classes to a non-default path
+ # @return [String]
+ # @api private
+ def cmd_path
+ return @cmd_path unless @cmd_path.nil?
+ @cmd_path = File.join(context.root_path, 'bin', cmd)
+ # Return the path to the command if it exists on disk, or we have a gemfile (i.e. Bundled install)
+ # The Bundle may be created after the prepare_invoke so if the file doesn't exist, it may not be an error
+ return @cmd_path if PDK::Util::Filesystem.exist?(@cmd_path) || !PDK::Util::Bundler::BundleHelper.new.gemfile.nil?
+ # But if there is no Gemfile AND cmd doesn't exist in the default path, we need to go searching...
+ @cmd_path = alternate_bin_paths.map { |alternate_path| File.join(alternate_path, cmd) }
+ .find { |path| PDK::Util::Filesystem.exist?(path) }
+ return @cmd_path unless @cmd_path.nil?
+ # If we can't find it anywhere, just let the OS find it
+ @cmd_path = cmd
+ end
+
+ # An array of command line arguments to pass to the command for validation
+ # @return Array[String]
+ # @abstract
+ def parse_options(_targets)
+ []
+ end
+
+ # Parses the output from the command and appends formatted events to the report.
+ # This is called for each command, which is a group of targets
+ #
+ # @param report [PDK::Report] The report to add events to
+ # @param result [Hash[Symbol => Object]] The result of validation command process
+ # @param targets [Array[String]] The targets for this command result
+ # @api private
+ # @see PDK::CLI::Exec::Command.execute!
+ # @abstract
+ def parse_output(_report, _result, _targets); end
+
+ # Prepares for invokation by parsing targets and creating the needed commands.
+ # @api private
+ # @see PDK::Validate::Validator.prepare_invoke!
+ def prepare_invoke!
+ return if @prepared
+ super
+
+ @targets, @skipped, @invalid = parse_targets
+ @targets = [] if @targets.nil?
+
+ target_groups = if @targets.empty? && allow_empty_targets?
+ # If we have no targets and we allow empty targets, create an empty target group list
+ [[]]
+ elsif invoke_style == :per_target
+ # If invoking :per_target, split the targets array into an array of
+ # single element arrays (one per target).
+ @targets.combination(1).to_a.compact
+ else
+ # Else we're invoking :once, wrap the targets array in another array. This is so we
+ # can loop through the invokes with the same logic, regardless of which invoke style
+ # is needed.
+ @targets.each_slice(1000).to_a.compact
+ end
+
+ # Register all of the commands for all of the targets
+ @commands = []
+ target_groups.each do |invokation_targets|
+ next if invokation_targets.empty? && !allow_empty_targets?
+ cmd_argv = parse_options(invokation_targets).unshift(cmd_path).compact
+ cmd_argv.unshift(File.join(PDK::Util::RubyVersion.bin_path, 'ruby.exe'), '-W0') if Gem.win_platform?
+
+ command = PDK::CLI::Exec::Command.new(*cmd_argv).tap do |c|
+ c.context = :module
+ c.environment = { 'PUPPET_GEM_VERSION' => options[:puppet] } if options[:puppet]
+
+ if spinners_enabled?
+ parent_validator = options[:parent_validator]
+ if parent_validator.nil? || parent_validator.spinner.nil? || !parent_validator.spinner.is_a?(TTY::Spinner::Multi)
+ c.add_spinner(spinner_text_for_targets(invokation_targets))
+ else
+ spinner = TTY::Spinner.new("[:spinner] #{spinner_text_for_targets(invokation_targets)}", PDK::CLI::Util.spinner_opts_for_platform)
+ parent_validator.spinner.register(spinner)
+ c.register_spinner(spinner, PDK::CLI::Util.spinner_opts_for_platform)
+ end
+ end
+ end
+
+ @commands << { command: command, invokation_targets: invokation_targets }
+ end
+ nil
+ end
+
+ # Invokes the prepared commands as an ExecGroup
+ # @see PDK::Validate::Validator.invoke
+ def invoke(report)
+ prepare_invoke!
+
+ process_skipped(report, @skipped)
+ process_invalid(report, @invalid)
+
+ # Nothing to execute so return success
+ return 0 if @commands.empty?
+
+ # If there's no Gemfile, then we can't ensure the binstubs are correct
+ PDK::Util::Bundler.ensure_binstubs!(cmd) unless PDK::Util::Bundler::BundleHelper.new.gemfile.nil?
+
+ exec_group = PDK::CLI::ExecGroup.create(name, { parallel: false }, options)
+
+ # Register all of the commands for all of the targets
+ @commands.each do |item|
+ command = item[:command]
+ invokation_targets = item[:invokation_targets]
+
+ exec_group.register do
+ result = command.execute!
+ begin
+ parse_output(report, result, invokation_targets.compact)
+ rescue PDK::Validate::ParseOutputError => e
+ $stderr.puts e.message
+ end
+ result[:exit_code]
+ end
+ end
+
+ # Now execute and get the return code
+ exec_group.exit_code
+ end
+ end
+ end
+end