Merge pull request #5618 from inspec/nm/check-cookstyle

Integrate InSpec check with Cookstyle
This commit is contained in:
Clinton Wolfe 2021-10-25 19:32:45 -04:00 committed by GitHub
commit 497cf9ab98
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 102 additions and 9 deletions

1
.gitignore vendored
View file

@ -28,6 +28,7 @@ inspec-deprecations-in-cfg.txt
inspec-deprecations-in-lib.txt inspec-deprecations-in-lib.txt
kitchen.local.yml kitchen.local.yml
meta-profile-0.2.0.tar.gz meta-profile-0.2.0.tar.gz
inheritance-1.0.0.tar.gz
omnibus/.cache omnibus/.cache
omnibus/pkg omnibus/pkg
profile-1.0.0.tar.gz profile-1.0.0.tar.gz

View file

@ -60,7 +60,7 @@ inspec automate SUBCOMMAND
## check ## check
Verify metadata in inspec.yml. Verify control data has fields (title, description, impact) defined and that all controls have visible tests. Verify the metadata in the inspec.yml file, verify that control blocks have the correct fields (title, description, impact) defined, that all controls have visible tests, and that controls are not using deprecated InSpec DSL code.
### Syntax ### Syntax

View file

@ -34,4 +34,7 @@ Gem::Specification.new do |spec|
spec.add_dependency "train-aws", "~> 0.2" spec.add_dependency "train-aws", "~> 0.2"
spec.add_dependency "train-winrm", "~> 0.2" spec.add_dependency "train-winrm", "~> 0.2"
spec.add_dependency "mongo", "= 2.13.2" # 2.14 introduces a broken symlink in mongo-2.14.0/spec/support/ocsp spec.add_dependency "mongo", "= 2.13.2" # 2.14 introduces a broken symlink in mongo-2.14.0/spec/support/ocsp
# checks code offenses with inspec check
spec.add_dependency "cookstyle"
end end

View file

@ -122,8 +122,8 @@ class Inspec::InspecCLI < Inspec::BaseCLI
end end
puts puts
if result[:errors].empty? && result[:warnings].empty? if result[:errors].empty? && result[:warnings].empty? && result[:offenses].empty?
ui.plain_line("No errors or warnings") ui.plain_line("No errors, warnings, or offenses")
else else
item_msg = lambda { |item| item_msg = lambda { |item|
pos = [item[:file], item[:line], item[:column]].compact.join(":") pos = [item[:file], item[:line], item[:column]].compact.join(":")
@ -135,11 +135,18 @@ class Inspec::InspecCLI < Inspec::BaseCLI
puts puts
unless result[:offenses].empty?
puts "Offenses:\n"
result[:offenses].each { |item| ui.cyan(" #{Inspec::UI::GLYPHS[:script_x]} #{item_msg.call(item)}\n\n") }
end
offenses = ui.cyan("#{result[:offenses].length} offenses", print: false)
errors = ui.red("#{result[:errors].length} errors", print: false) errors = ui.red("#{result[:errors].length} errors", print: false)
warnings = ui.yellow("#{result[:warnings].length} warnings", print: false) warnings = ui.yellow("#{result[:warnings].length} warnings", print: false)
ui.plain_line("Summary: #{errors}, #{warnings}") ui.plain_line("Summary: #{errors}, #{warnings}, #{offenses}")
end end
end end
ui.exit Inspec::UI::EXIT_USAGE_ERROR unless result[:summary][:valid] ui.exit Inspec::UI::EXIT_USAGE_ERROR unless result[:summary][:valid]
rescue StandardError => e rescue StandardError => e
pretty_handle_exception(e) pretty_handle_exception(e)

View file

@ -448,6 +448,43 @@ module Inspec
res res
end end
def cookstyle_linting_check
msgs = []
output = cookstyle_rake_output.split("Offenses:").last
msgs = output.split("\n").select { |x| x =~ /[A-Z]:/ } unless output.nil?
msgs
end
# Cookstyle linting rake run output
def cookstyle_rake_output
require "cookstyle"
require "rubocop/rake_task"
begin
RuboCop::RakeTask.new(:cookstyle_lint) do |spec|
spec.options += [
"--display-cop-names",
"--parallel",
"--only=InSpec/Deprecations",
]
spec.patterns += Dir.glob("#{@target}/**/*.rb").reject { |f| File.directory?(f) }
spec.fail_on_error = false
end
rescue LoadError
puts "Rubocop is not available. Install the rubocop gem to run the lint tests."
end
begin
stdout = StringIO.new
$stdout = stdout
Rake::Task["cookstyle_lint"].invoke
$stdout = STDOUT
Rake.application["cookstyle_lint"].reenable
stdout.string
rescue => e
puts "Cookstyle lint checks could not be performed. Error while running cookstyle - #{e}"
""
end
end
# Check if the profile is internally well-structured. The logger will be # Check if the profile is internally well-structured. The logger will be
# used to print information on errors and warnings which are found. # used to print information on errors and warnings which are found.
# #
@ -464,6 +501,7 @@ module Inspec
}, },
errors: [], errors: [],
warnings: [], warnings: [],
offenses: [],
} }
entry = lambda { |file, line, column, control, msg| entry = lambda { |file, line, column, control, msg|
@ -486,6 +524,10 @@ module Inspec
result[:errors].push(entry.call(file, line, column, control, msg)) result[:errors].push(entry.call(file, line, column, control, msg))
} }
offense = lambda { |file, line, column, control, msg|
result[:offenses].push(entry.call(file, line, column, control, msg))
}
@logger.info "Checking profile in #{@target}" @logger.info "Checking profile in #{@target}"
meta_path = @source_reader.target.abs_path(@source_reader.metadata.ref) meta_path = @source_reader.target.abs_path(@source_reader.metadata.ref)
@ -548,8 +590,15 @@ module Inspec
warn.call(sfile, sline, nil, id, "Control #{id} has no tests defined") if control[:checks].nil? || control[:checks].empty? warn.call(sfile, sline, nil, id, "Control #{id} has no tests defined") if control[:checks].nil? || control[:checks].empty?
end end
# profile is valid if we could not find any error # Running cookstyle to check for code offenses
result[:summary][:valid] = result[:errors].empty? cookstyle_linting_check.each do |lint_output|
data = lint_output.split(":")
msg = "#{data[-2]}:#{data[-1]}"
offense.call(data[0], data[1], data[2], nil, msg)
end
# profile is valid if we could not find any error & offenses
result[:summary][:valid] = result[:errors].empty? && result[:offenses].empty?
@logger.info "Control definitions OK." if result[:warnings].empty? @logger.info "Control definitions OK." if result[:warnings].empty?
result result

View file

@ -235,6 +235,14 @@ module FunctionalHelper
end end
end end
def prepare_profiles(dir = nil, &block)
Dir.mktmpdir do |tmpdir|
FileUtils.cp_r(profile_path, tmpdir)
bn = File.basename(profile_path)
yield(File.join(tmpdir, bn, dir.to_s))
end
end
private private
def assemble_env_prefix(env = {}) def assemble_env_prefix(env = {})

View file

@ -102,7 +102,7 @@ describe "inspec archive" do
end end
it "vendors dependencies by default" do it "vendors dependencies by default" do
prepare_examples("meta-profile") do |dir| prepare_profiles("dependencies/inheritance") do |dir|
out = inspec("archive " + dir + " --output " + dst.path) out = inspec("archive " + dir + " --output " + dst.path)
_(out.stderr).must_equal "" _(out.stderr).must_equal ""

View file

@ -117,4 +117,18 @@ describe "inspec check" do
assert_exit_code 1, out assert_exit_code 1, out
end end
end end
describe "inspec check also check for cookstyle offenses" do
it "finds no offenses in a complete profile" do
out = inspec("check #{profile_path}/complete-profile")
_(out.stdout).must_match(/No errors, warnings, or offenses/)
assert_exit_code 0, out
end
it "fails and returns offenses in a profile" do
out = inspec("check #{profile_path}/inputs/metadata-basic")
_(out.stdout).must_match(/1 offenses/)
assert_exit_code 1, out
end
end
end end

View file

@ -155,7 +155,7 @@ describe "example inheritance profile" do
end end
it "use lockfile in tarball" do it "use lockfile in tarball" do
prepare_examples("meta-profile") do |dir| prepare_profiles("dependencies/inheritance") do |dir|
# ensure the profile is vendored and packaged as tar # ensure the profile is vendored and packaged as tar
out = inspec("vendor " + dir + " --overwrite") out = inspec("vendor " + dir + " --overwrite")
@ -172,7 +172,7 @@ describe "example inheritance profile" do
# TODO: split # TODO: split
# execute json command # execute json command
out = inspec("json meta-profile-0.2.0.tar.gz -l debug") out = inspec("json inheritance-1.0.0.tar.gz -l debug")
_(out.stdout.scan(/Fetching URL:/).length).must_equal 0 _(out.stdout.scan(/Fetching URL:/).length).must_equal 0
_(out.stdout).wont_match(/Fetching URL:/) _(out.stdout).wont_match(/Fetching URL:/)

View file

@ -124,6 +124,7 @@ describe Inspec::Profile do
_(result[:summary][:controls]).must_equal 0 _(result[:summary][:controls]).must_equal 0
_(result[:errors].length).must_equal 1 _(result[:errors].length).must_equal 1
_(result[:warnings].length).must_equal 5 _(result[:warnings].length).must_equal 5
_(result[:offenses]).must_be_empty
end end
end end
@ -148,6 +149,7 @@ describe Inspec::Profile do
_(result[:summary][:controls]).must_equal 0 _(result[:summary][:controls]).must_equal 0
_(result[:errors]).must_be_empty _(result[:errors]).must_be_empty
_(result[:warnings].length).must_equal 1 _(result[:warnings].length).must_equal 1
_(result[:offenses]).must_be_empty
end end
end end
@ -171,6 +173,7 @@ describe Inspec::Profile do
_(result[:summary][:controls]).must_equal 1 _(result[:summary][:controls]).must_equal 1
_(result[:errors]).must_be_empty _(result[:errors]).must_be_empty
_(result[:warnings]).must_be_empty _(result[:warnings]).must_be_empty
_(result[:offenses]).must_be_empty
end end
end end
@ -196,6 +199,7 @@ describe Inspec::Profile do
_(result[:summary][:controls]).must_equal 1 _(result[:summary][:controls]).must_equal 1
_(result[:errors]).must_be_empty _(result[:errors]).must_be_empty
_(result[:warnings]).must_be_empty _(result[:warnings]).must_be_empty
_(result[:offenses]).must_be_empty
end end
end end
@ -221,6 +225,7 @@ describe Inspec::Profile do
_(result[:summary][:controls]).must_equal 1 _(result[:summary][:controls]).must_equal 1
_(result[:errors]).must_be_empty _(result[:errors]).must_be_empty
_(result[:warnings]).must_be_empty _(result[:warnings]).must_be_empty
_(result[:offenses]).must_be_empty
end end
end end
@ -246,6 +251,7 @@ describe Inspec::Profile do
_(result[:summary][:controls]).must_equal 1 _(result[:summary][:controls]).must_equal 1
_(result[:errors]).must_be_empty _(result[:errors]).must_be_empty
_(result[:warnings]).must_be_empty _(result[:warnings]).must_be_empty
_(result[:offenses]).must_be_empty
end end
end end
@ -272,6 +278,7 @@ describe Inspec::Profile do
_(result[:summary][:controls]).must_equal 0 _(result[:summary][:controls]).must_equal 0
_(result[:errors].length).must_equal 1 _(result[:errors].length).must_equal 1
_(result[:warnings].length).must_equal 1 _(result[:warnings].length).must_equal 1
_(result[:offenses]).must_be_empty
end end
end end
@ -289,6 +296,7 @@ describe Inspec::Profile do
logger.verify logger.verify
_(result[:warnings]).must_be_empty _(result[:warnings]).must_be_empty
_(result[:errors].length).must_equal 1 _(result[:errors].length).must_equal 1
_(result[:offenses]).must_be_empty
end end
end end
@ -316,6 +324,7 @@ describe Inspec::Profile do
_(result[:summary][:controls]).must_equal 0 _(result[:summary][:controls]).must_equal 0
_(result[:errors]).must_be_empty _(result[:errors]).must_be_empty
_(result[:warnings].length).must_equal 2 _(result[:warnings].length).must_equal 2
_(result[:offenses]).must_be_empty
end end
describe "shows no warning if license is spdx" do describe "shows no warning if license is spdx" do
@ -341,6 +350,7 @@ describe Inspec::Profile do
_(result[:summary][:controls]).must_equal 0 _(result[:summary][:controls]).must_equal 0
_(result[:errors]).must_be_empty _(result[:errors]).must_be_empty
_(result[:warnings].length).must_equal 1 _(result[:warnings].length).must_equal 1
_(result[:offenses]).must_be_empty
end end
end end
@ -367,6 +377,7 @@ describe Inspec::Profile do
_(result[:summary][:controls]).must_equal 0 _(result[:summary][:controls]).must_equal 0
_(result[:errors]).must_be_empty _(result[:errors]).must_be_empty
_(result[:warnings].length).must_equal 1 _(result[:warnings].length).must_equal 1
_(result[:offenses]).must_be_empty
end end
end end