Class: Dash::Dockerfile::Hadolint

Inherits:
Object
  • Object
show all
Defined in:
lib/dash/dockerfile/hadolint.rb

Overview

Optional supplement to the built-in rules: whatever hadolint has to say about the same file, when the operator already has it installed.

Run as a plain local process rather than through SSHKit, so the command sequence a deploy prints — and the cost-guard test that pins it — is unchanged. --no-fail keeps its exit status out of the deploy, and anything that goes wrong becomes one informational finding rather than an exception.

Constant Summary collapse

EXECUTABLE =
"hadolint".freeze
SEVERITIES =

hadolint's own levels. error is the only one worth a warning next to a deploy; the rest are style notes that should not compete with a measured finding.

{ "error" => :warn }.freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(path:, file: path) ⇒ Hadolint

Returns a new instance of Hadolint.



29
30
31
32
# File 'lib/dash/dockerfile/hadolint.rb', line 29

def initialize(path:, file: path)
  @path = path
  @file = file
end

Class Method Details

.available? ⇒ Boolean

No shell: PATH is walked directly, so a directory with a space or a semicolon in it cannot turn a lookup into a command.

Returns:

  • (Boolean)


21
22
23
24
25
26
# File 'lib/dash/dockerfile/hadolint.rb', line 21

def available?
  ENV["PATH"].to_s.split(::File::PATH_SEPARATOR).any? do |directory|
    path = ::File.join(directory, EXECUTABLE)
    ::File.executable?(path) && !::File.directory?(path)
  end
end

Instance Method Details

#findings ⇒ Object



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/dash/dockerfile/hadolint.rb', line 34

def findings
  return [] unless self.class.available?

  output, status = Open3.capture2(EXECUTABLE, "--format", "json", "--no-fail", @file)
  return unavailable("exited #{status.exitstatus}") unless status.success?
  return [] if output.strip.empty?

  issues = JSON.parse(output)
  return unparsable("expected a JSON array, got #{issues.class.name.downcase}") unless issues.is_a?(Array)

  issues.map { |issue| finding_for(issue) }
# Only JSON.parse raises this, so it is the one error that means "it ran fine, dash
# could not read what it printed". Anything else that raises in here is dash's own.
rescue JSON::ParserError => e
  unparsable(e.message.truncate(80))
rescue StandardError => e
  unavailable(e.message)
end