Class: PDK::Validate::ExternalCommandValidator

Inherits:
InvokableValidator show all
Defined in:
lib/pdk/validate/external_command_validator.rb

Overview

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 Also:

Instance Attribute Summary collapse

Attributes inherited from Validator

#context, #options, #prepared

Instance Method Summary collapse

Methods inherited from InvokableValidator

#allow_empty_targets?, #fnmatch?, #ignore_dotfiles?, #invoke_style, #parse_targets, #pattern, #pattern_ignore, #process_invalid, #process_skipped, #spinner_text, #valid_in_context?

Methods inherited from Validator

#initialize, #name, #spinner_text, #spinners_enabled?, #start_spinner, #stop_spinner, #valid_in_context?

Constructor Details

This class inherits a constructor from PDK::Validate::Validator

Instance Attribute Details

#commandsObject (readonly)

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



52
53
54
# File 'lib/pdk/validate/external_command_validator.rb', line 52

def commands
  @commands
end

Instance Method Details

#alternate_bin_pathsArray[String]

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Alternate paths which the command (cmd) may exist in. Typically other ruby gem caches, or packaged installation bin directories.

Returns:

  • (Array[String])


74
75
76
77
78
79
80
81
# File 'lib/pdk/validate/external_command_validator.rb', line 74

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

#cmdString

This method is abstract.

The name of the command to be run for validation

Returns:

  • (String)


68
# File 'lib/pdk/validate/external_command_validator.rb', line 68

def cmd; end

#cmd_pathString

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

The full path to the command (cmd) Can be overridden in child classes to a non-default path

Returns:

  • (String)


87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/pdk/validate/external_command_validator.rb', line 87

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

#invoke(report) ⇒ Object

Invokes the prepared commands as an ExecGroup

See Also:

  • Validator.invoke


178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
# File 'lib/pdk/validate/external_command_validator.rb', line 178

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

#parse_options(_targets) ⇒ Object

This method is abstract.

An array of command line arguments to pass to the command for validation



107
108
109
# File 'lib/pdk/validate/external_command_validator.rb', line 107

def parse_options(_targets)
  []
end

#parse_output(_report, _result, _targets) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

This method is abstract.

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

Parameters:

  • report (PDK::Report)

    The report to add events to

  • result (Hash[Symbol => Object])

    The result of validation command process

  • targets (Array[String])

    The targets for this command result

See Also:

  • CLI::Exec::Command.execute!


120
# File 'lib/pdk/validate/external_command_validator.rb', line 120

def parse_output(_report, _result, _targets); end

#prepare_invoke!Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Prepares for invokation by parsing targets and creating the needed commands.

See Also:

  • Validator.prepare_invoke!


125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# File 'lib/pdk/validate/external_command_validator.rb', line 125

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

#spinnerObject

See Also:

  • Validator.spinner


55
56
57
58
# File 'lib/pdk/validate/external_command_validator.rb', line 55

def spinner
  # The validator has sub-commands with their own spinners.
  nil
end

#spinner_text_for_targets(targets) ⇒ String

This method is abstract.

Calculates the text of the spinner based on the target list

Returns:

  • (String)


63
# File 'lib/pdk/validate/external_command_validator.rb', line 63

def spinner_text_for_targets(targets); end