mirror of
https://github.com/inspec/inspec
synced 2024-11-27 15:10:44 +00:00
Merge pull request #2601 from chef/jq/add_generic_report_output
Fix runner report and json newline
This commit is contained in:
commit
9420204d60
12 changed files with 126 additions and 52 deletions
|
@ -210,10 +210,12 @@ module Inspec
|
|||
opts = BaseCLI.default_options[type] unless type.nil? || BaseCLI.default_options[type].nil?
|
||||
|
||||
# merge in any options from json-config
|
||||
opts.merge!(options_json)
|
||||
json_config = options_json
|
||||
opts.merge!(json_config)
|
||||
|
||||
# remove the default reporter if we are setting a legacy format on the cli
|
||||
opts.delete('reporter') if options['format']
|
||||
# or via json-config
|
||||
opts.delete('reporter') if options['format'] || json_config['format']
|
||||
|
||||
# merge in any options defined via thor
|
||||
opts.merge!(options)
|
||||
|
@ -299,7 +301,7 @@ module Inspec
|
|||
Inspec::Log.init(loc)
|
||||
Inspec::Log.level = get_log_level(o.log_level)
|
||||
|
||||
o[:logger] = Logger.new(STDOUT)
|
||||
o[:logger] = Logger.new(loc)
|
||||
# output json if we have activated the json formatter
|
||||
if o['log-format'] == 'json'
|
||||
o[:logger].formatter = Logger::JSONFormatter.new
|
||||
|
|
|
@ -6,7 +6,7 @@ require 'inspec/reporters/junit'
|
|||
|
||||
module Inspec::Reporters
|
||||
def self.render(reporter, run_data)
|
||||
name, config = reporter
|
||||
name, config = reporter.dup
|
||||
config[:run_data] = run_data
|
||||
case name
|
||||
when 'cli'
|
||||
|
@ -27,7 +27,24 @@ module Inspec::Reporters
|
|||
if config['file']
|
||||
File.write(config['file'], output)
|
||||
elsif config['stdout'] == true
|
||||
puts output
|
||||
print output
|
||||
STDOUT.flush
|
||||
end
|
||||
end
|
||||
|
||||
def self.report(reporter, run_data)
|
||||
name, config = reporter.dup
|
||||
config[:run_data] = run_data
|
||||
case name
|
||||
when 'json'
|
||||
reporter = Inspec::Reporters::Json.new(config)
|
||||
when 'json-min'
|
||||
reporter = Inspec::Reporters::JsonMin.new(config)
|
||||
else
|
||||
# use base run_data hash for any other report
|
||||
return run_data
|
||||
end
|
||||
|
||||
reporter.report
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,8 +7,9 @@ module Inspec::Reporters
|
|||
@output = ''
|
||||
end
|
||||
|
||||
def output(str)
|
||||
@output << "#{str}\n"
|
||||
def output(str, newline = true)
|
||||
@output << str
|
||||
@output << "\n" if newline
|
||||
end
|
||||
|
||||
def rendered_output
|
||||
|
|
|
@ -5,7 +5,11 @@ require 'json'
|
|||
module Inspec::Reporters
|
||||
class Json < Base
|
||||
def render
|
||||
report = {
|
||||
output(report.to_json, false)
|
||||
end
|
||||
|
||||
def report
|
||||
{
|
||||
platform: platform,
|
||||
profiles: profiles,
|
||||
statistics: { duration: run_data[:statistics][:duration] },
|
||||
|
@ -13,8 +17,6 @@ module Inspec::Reporters
|
|||
controls: controls,
|
||||
other_checks: run_data[:other_checks],
|
||||
}
|
||||
|
||||
output(report.to_json)
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -4,7 +4,11 @@ require 'json'
|
|||
|
||||
module Inspec::Reporters
|
||||
class JsonMin < Base
|
||||
def render # rubocop:disable Metrics/AbcSize
|
||||
def render
|
||||
output(report.to_json, false)
|
||||
end
|
||||
|
||||
def report # rubocop:disable Metrics/AbcSize
|
||||
report = {
|
||||
controls: [],
|
||||
statistics: { duration: run_data[:statistics][:duration] },
|
||||
|
@ -38,7 +42,7 @@ module Inspec::Reporters
|
|||
end
|
||||
end
|
||||
|
||||
output(report.to_json)
|
||||
report
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -31,8 +31,6 @@ module Inspec
|
|||
class Runner
|
||||
extend Forwardable
|
||||
|
||||
def_delegator :@test_collector, :report
|
||||
|
||||
attr_reader :backend, :rules, :attributes
|
||||
def initialize(conf = {})
|
||||
@rules = []
|
||||
|
@ -112,6 +110,10 @@ module Inspec
|
|||
end
|
||||
end
|
||||
|
||||
def report
|
||||
Inspec::Reporters.report(@conf['reporter'].first, @run_data)
|
||||
end
|
||||
|
||||
def write_lockfile(profile)
|
||||
return false if !profile.writable?
|
||||
|
||||
|
@ -125,8 +127,9 @@ module Inspec
|
|||
end
|
||||
|
||||
def run_tests(with = nil)
|
||||
status, run_data = @test_collector.run(with)
|
||||
render_output(run_data)
|
||||
status, @run_data = @test_collector.run(with)
|
||||
# dont output anything if we want a report
|
||||
render_output(@run_data) unless @conf['report']
|
||||
status
|
||||
end
|
||||
|
||||
|
|
|
@ -78,15 +78,6 @@ module Inspec
|
|||
[status, @formatter.run_data]
|
||||
end
|
||||
|
||||
# Provide an output hash of the run's report
|
||||
#
|
||||
# @return [Hash] a run's output hash
|
||||
def report
|
||||
reporter = @formatter || RSpec.configuration.formatters[0]
|
||||
return nil if reporter.nil? || !reporter.respond_to?(:output_hash)
|
||||
reporter.output_hash
|
||||
end
|
||||
|
||||
# Empty the list of registered tests.
|
||||
#
|
||||
# @return [nil]
|
||||
|
@ -140,11 +131,6 @@ module Inspec
|
|||
RSpec.configuration.add_formatter(Inspec::Formatters::ShowProgress, $stderr) if @conf[:show_progress]
|
||||
set_optional_formatters
|
||||
RSpec.configuration.color = @conf['color']
|
||||
setup_reporting if @conf['report']
|
||||
end
|
||||
|
||||
def setup_reporting
|
||||
RSpec.configuration.add_formatter(Inspec::RSpecReporter)
|
||||
end
|
||||
|
||||
# Make sure that all RSpec example groups use the provided ID.
|
||||
|
@ -174,12 +160,4 @@ module Inspec
|
|||
metadata[:source_location] = rule.instance_variable_get(:@__source_location)
|
||||
end
|
||||
end
|
||||
|
||||
class RSpecReporter < RSpec::Core::Formatters::JsonFormatter
|
||||
RSpec::Core::Formatters.register Inspec::RSpecReporter
|
||||
|
||||
def initialize(*)
|
||||
super(StringIO.new)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,29 +8,30 @@ describe 'BaseCLI' do
|
|||
let(:cli) { Inspec::BaseCLI.new }
|
||||
|
||||
describe 'merge_options' do
|
||||
let(:default_options) do
|
||||
{ exec: { 'reporter' => ['json'], 'backend_cache' => false }}
|
||||
end
|
||||
|
||||
it 'cli defaults populate correctly' do
|
||||
default_options = { exec: { format: 'json', backend_cache: false }}
|
||||
Inspec::BaseCLI.stubs(:default_options).returns(default_options)
|
||||
|
||||
opts = cli.send(:merged_opts, :exec)
|
||||
expected = { 'format' => 'json', 'backend_cache' => false }
|
||||
expected = {"backend_cache"=>false, "reporter"=>{"json"=>{"stdout"=>true}}}
|
||||
opts.must_equal expected
|
||||
end
|
||||
|
||||
it 'json-config options override cli defaults' do
|
||||
default_options = { exec: { format: 'json', backend_cache: false }}
|
||||
Inspec::BaseCLI.stubs(:default_options).returns(default_options)
|
||||
|
||||
parsed_json = { 'backend_cache' => true }
|
||||
cli.expects(:options_json).returns(parsed_json)
|
||||
|
||||
opts = cli.send(:merged_opts, :exec)
|
||||
expected = { 'format' => 'json', 'backend_cache' => true }
|
||||
expected = {"backend_cache"=>true, "reporter"=>{"json"=>{"stdout"=>true}}}
|
||||
opts.must_equal expected
|
||||
end
|
||||
|
||||
it 'cli options override json-config and default' do
|
||||
default_options = { exec: { format: 'json', backend_cache: false }}
|
||||
Inspec::BaseCLI.stubs(:default_options).returns(default_options)
|
||||
|
||||
parsed_json = { 'backend_cache' => false }
|
||||
|
@ -40,18 +41,68 @@ describe 'BaseCLI' do
|
|||
cli.instance_variable_set(:@options, cli_options)
|
||||
|
||||
opts = cli.send(:merged_opts, :exec)
|
||||
expected = { 'format' => 'json', 'backend_cache' => true }
|
||||
expected = {"backend_cache"=>true, "reporter"=>{"json"=>{"stdout"=>true}}}
|
||||
opts.must_equal expected
|
||||
end
|
||||
|
||||
it 'make sure shell does not get exec defaults' do
|
||||
default_options = { exec: { format: 'json', backend_cache: false }}
|
||||
Inspec::BaseCLI.stubs(:default_options).returns(default_options)
|
||||
|
||||
opts = cli.send(:merged_opts)
|
||||
expected = {}
|
||||
opts.must_equal expected
|
||||
end
|
||||
|
||||
it 'make sure default reporter is overriden by json-config format' do
|
||||
default_options['reporter'] = ['cli']
|
||||
Inspec::BaseCLI.stubs(:default_options).returns(default_options)
|
||||
parsed_json = { 'format' => 'json' }
|
||||
cli.expects(:options_json).returns(parsed_json)
|
||||
|
||||
opts = cli.send(:merged_opts, :exec)
|
||||
expected = {"backend_cache"=>false, "reporter"=>{"json"=>{"stdout"=>true}}}
|
||||
opts.must_equal expected
|
||||
end
|
||||
end
|
||||
|
||||
describe 'configure_logger' do
|
||||
let(:options) do
|
||||
o = {
|
||||
'log_location' => STDERR,
|
||||
'log_level' => 'debug',
|
||||
'reporter' => {
|
||||
'json' => {
|
||||
'stdout' => true,
|
||||
},
|
||||
},
|
||||
}
|
||||
Thor::CoreExt::HashWithIndifferentAccess.new(o)
|
||||
end
|
||||
let(:format) do
|
||||
device = options[:logger].instance_variable_get(:"@logdev")
|
||||
device.instance_variable_get(:"@dev")
|
||||
end
|
||||
|
||||
it 'sets to stderr for log_location' do
|
||||
cli.send(:configure_logger, options)
|
||||
format.must_equal STDERR
|
||||
end
|
||||
|
||||
it 'sets to stderr for json' do
|
||||
options.delete('log_location')
|
||||
options.delete('log_level')
|
||||
cli.send(:configure_logger, options)
|
||||
format.must_equal STDERR
|
||||
end
|
||||
|
||||
it 'sets defaults to stdout for everything else' do
|
||||
options.delete('log_location')
|
||||
options.delete('log_level')
|
||||
options.delete('reporter')
|
||||
|
||||
cli.send(:configure_logger, options)
|
||||
format.must_equal STDOUT
|
||||
end
|
||||
end
|
||||
|
||||
describe 'parse_reporters' do
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"controls":[{"id":"(generated from example.rb:7 871cd54043069c5c4f6e382fd5627830)","profile_id":"long_commands","profile_sha256":"4f816f8cf18f165f05f1cf20936aaad06a15287de3f578891197647ca05c7df4","status":"passed","code_desc":"File /tmp should be directory"},{"id":"tmp-1.0","profile_id":"long_commands","profile_sha256":"4f816f8cf18f165f05f1cf20936aaad06a15287de3f578891197647ca05c7df4","status":"passed","code_desc":"File /tmp should be directory"},{"id":"(generated from example.rb:21 2ff474c5357e7070f4c3efa932032dcb)","profile_id":"long_commands","profile_sha256":"4f816f8cf18f165f05f1cf20936aaad06a15287de3f578891197647ca05c7df4","status":"failed","code_desc":"gem package rubocop should be installed"},{"id":"(generated from run_command.rb:5 a411d4ded1530b2f48170840e1127584)","profile_id":"long_commands","profile_sha256":"4f816f8cf18f165f05f1cf20936aaad06a15287de3f578891197647ca05c7df4","status":"passed","code_desc":"Command whoami stdout should eq \"jquick\\n\""}],"statistics":{"duration":0.039182},"version":"1.49.2"}
|
||||
{"controls":[{"id":"(generated from example.rb:7 871cd54043069c5c4f6e382fd5627830)","profile_id":"long_commands","profile_sha256":"4f816f8cf18f165f05f1cf20936aaad06a15287de3f578891197647ca05c7df4","status":"passed","code_desc":"File /tmp should be directory"},{"id":"tmp-1.0","profile_id":"long_commands","profile_sha256":"4f816f8cf18f165f05f1cf20936aaad06a15287de3f578891197647ca05c7df4","status":"passed","code_desc":"File /tmp should be directory"},{"id":"(generated from example.rb:21 2ff474c5357e7070f4c3efa932032dcb)","profile_id":"long_commands","profile_sha256":"4f816f8cf18f165f05f1cf20936aaad06a15287de3f578891197647ca05c7df4","status":"failed","code_desc":"gem package rubocop should be installed"},{"id":"(generated from run_command.rb:5 a411d4ded1530b2f48170840e1127584)","profile_id":"long_commands","profile_sha256":"4f816f8cf18f165f05f1cf20936aaad06a15287de3f578891197647ca05c7df4","status":"passed","code_desc":"Command whoami stdout should eq \"jquick\\n\""}],"statistics":{"duration":0.039182},"version":"1.49.2"}
|
|
@ -1 +1 @@
|
|||
{"platform":{"name":"mac_os_x","release":"17.2.0"},"profiles":[{"name":"long_commands","version":"0.1.0","sha256":"4f816f8cf18f165f05f1cf20936aaad06a15287de3f578891197647ca05c7df4","title":"InSpec Profile","maintainer":"The Authors","summary":"An InSpec Compliance Profile","license":"Apache-2.0","copyright":"The Authors","copyright_email":"you@example.com","supports":[{"os-family":"bds"},{"os-name":"mac_os_x","release":"17.*"}],"attributes":[],"groups":[{"id":"controls/example.rb","controls":["(generated from example.rb:7 871cd54043069c5c4f6e382fd5627830)","tmp-1.0","(generated from example.rb:21 2ff474c5357e7070f4c3efa932032dcb)"],"title":"sample section"},{"id":"controls/run_command.rb","controls":["(generated from run_command.rb:5 a411d4ded1530b2f48170840e1127584)"]}],"controls":[{"id":"(generated from example.rb:7 871cd54043069c5c4f6e382fd5627830)","title":null,"desc":null,"impact":0.5,"refs":[],"tags":{},"code":"","source_location":{"line":89,"ref":"/Users/jquick/Chef/inspec/lib/inspec/control_eval_context.rb"},"results":[{"status":"passed","code_desc":"File /tmp should be directory","run_time":0.002058,"start_time":"2018-01-05 11:43:04 -0500"}]},{"id":"tmp-1.0","title":"Create /tmp directory","desc":"An optional description...","impact":0.7,"refs":[],"tags":{},"code":"control 'tmp-1.0' do # A unique ID for this control\n impact 0.7 # The criticality, if this control fails.\n title 'Create /tmp directory' # A human-readable title\n desc 'An optional description...'\n describe file('/tmp') do # The actual test\n it { should be_directory }\n end\nend\n","source_location":{"line":12,"ref":"../inspec-demo/_test/long_commands/controls/example.rb"},"results":[{"status":"passed","code_desc":"File /tmp should be directory","run_time":0.000102,"start_time":"2018-01-05 11:43:04 -0500"}]},{"id":"(generated from example.rb:21 2ff474c5357e7070f4c3efa932032dcb)","title":null,"desc":null,"impact":0.5,"refs":[],"tags":{},"code":"","source_location":{"line":89,"ref":"/Users/jquick/Chef/inspec/lib/inspec/control_eval_context.rb"},"results":[{"status":"failed","code_desc":"gem package rubocop should be installed","run_time":0.000168,"start_time":"2018-01-05 11:43:04 -0500"}]},{"id":"(generated from run_command.rb:5 a411d4ded1530b2f48170840e1127584)","title":null,"desc":null,"impact":0.5,"refs":[],"tags":{},"code":"","source_location":{"line":89,"ref":"/Users/jquick/Chef/inspec/lib/inspec/control_eval_context.rb"},"results":[{"status":"passed","code_desc":"Command whoami stdout should eq \"jquick\\n\"","run_time":0.034938,"start_time":"2018-01-05 11:43:04 -0500"}]}]}],"statistics":{"duration":0.039182},"version":"1.49.2","controls":[{"status":"passed","start_time":"2018-01-05 11:43:04 -0500","run_time":0.002058,"code_desc":"File /tmp should be directory"},{"status":"passed","start_time":"2018-01-05 11:43:04 -0500","run_time":0.000102,"code_desc":"File /tmp should be directory"},{"status":"passed","start_time":"2018-01-05 11:43:04 -0500","run_time":0.000168,"code_desc":"gem package rubocop should be installed"},{"status":"passed","start_time":"2018-01-05 11:43:04 -0500","run_time":0.034938,"code_desc":"Command whoami stdout should eq \"jquick\\n\""}],"other_checks":[]}
|
||||
{"platform":{"name":"mac_os_x","release":"17.2.0"},"profiles":[{"name":"long_commands","version":"0.1.0","sha256":"4f816f8cf18f165f05f1cf20936aaad06a15287de3f578891197647ca05c7df4","title":"InSpec Profile","maintainer":"The Authors","summary":"An InSpec Compliance Profile","license":"Apache-2.0","copyright":"The Authors","copyright_email":"you@example.com","supports":[{"os-family":"bds"},{"os-name":"mac_os_x","release":"17.*"}],"attributes":[],"groups":[{"id":"controls/example.rb","controls":["(generated from example.rb:7 871cd54043069c5c4f6e382fd5627830)","tmp-1.0","(generated from example.rb:21 2ff474c5357e7070f4c3efa932032dcb)"],"title":"sample section"},{"id":"controls/run_command.rb","controls":["(generated from run_command.rb:5 a411d4ded1530b2f48170840e1127584)"]}],"controls":[{"id":"(generated from example.rb:7 871cd54043069c5c4f6e382fd5627830)","title":null,"desc":null,"impact":0.5,"refs":[],"tags":{},"code":"","source_location":{"line":89,"ref":"/Users/jquick/Chef/inspec/lib/inspec/control_eval_context.rb"},"results":[{"status":"passed","code_desc":"File /tmp should be directory","run_time":0.002058,"start_time":"2018-01-05 11:43:04 -0500"}]},{"id":"tmp-1.0","title":"Create /tmp directory","desc":"An optional description...","impact":0.7,"refs":[],"tags":{},"code":"control 'tmp-1.0' do # A unique ID for this control\n impact 0.7 # The criticality, if this control fails.\n title 'Create /tmp directory' # A human-readable title\n desc 'An optional description...'\n describe file('/tmp') do # The actual test\n it { should be_directory }\n end\nend\n","source_location":{"line":12,"ref":"../inspec-demo/_test/long_commands/controls/example.rb"},"results":[{"status":"passed","code_desc":"File /tmp should be directory","run_time":0.000102,"start_time":"2018-01-05 11:43:04 -0500"}]},{"id":"(generated from example.rb:21 2ff474c5357e7070f4c3efa932032dcb)","title":null,"desc":null,"impact":0.5,"refs":[],"tags":{},"code":"","source_location":{"line":89,"ref":"/Users/jquick/Chef/inspec/lib/inspec/control_eval_context.rb"},"results":[{"status":"failed","code_desc":"gem package rubocop should be installed","run_time":0.000168,"start_time":"2018-01-05 11:43:04 -0500"}]},{"id":"(generated from run_command.rb:5 a411d4ded1530b2f48170840e1127584)","title":null,"desc":null,"impact":0.5,"refs":[],"tags":{},"code":"","source_location":{"line":89,"ref":"/Users/jquick/Chef/inspec/lib/inspec/control_eval_context.rb"},"results":[{"status":"passed","code_desc":"Command whoami stdout should eq \"jquick\\n\"","run_time":0.034938,"start_time":"2018-01-05 11:43:04 -0500"}]}]}],"statistics":{"duration":0.039182},"version":"1.49.2","controls":[{"status":"passed","start_time":"2018-01-05 11:43:04 -0500","run_time":0.002058,"code_desc":"File /tmp should be directory"},{"status":"passed","start_time":"2018-01-05 11:43:04 -0500","run_time":0.000102,"code_desc":"File /tmp should be directory"},{"status":"passed","start_time":"2018-01-05 11:43:04 -0500","run_time":0.000168,"code_desc":"gem package rubocop should be installed"},{"status":"passed","start_time":"2018-01-05 11:43:04 -0500","run_time":0.034938,"code_desc":"Command whoami stdout should eq \"jquick\\n\""}],"other_checks":[]}
|
|
@ -4,16 +4,24 @@ require 'helper'
|
|||
|
||||
describe Inspec::Reporters::JsonMin do
|
||||
let(:path) { File.expand_path(File.dirname(__FILE__)) }
|
||||
let(:report) do
|
||||
let(:report) do
|
||||
data = JSON.parse(File.read(path + '/../mock/reporters/run_data.json'), symbolize_names: true)
|
||||
Inspec::Reporters::JsonMin.new({ run_data: data })
|
||||
end
|
||||
|
||||
describe '#render' do
|
||||
it 'confirm render output' do
|
||||
cli_output = File.read(path + '/../mock/reporters/json_min_output')
|
||||
output = File.read(path + '/../mock/reporters/json_min_output')
|
||||
report.render
|
||||
report.rendered_output.must_equal cli_output
|
||||
report.rendered_output.must_equal output
|
||||
end
|
||||
end
|
||||
|
||||
describe '#report' do
|
||||
it 'confirm report output' do
|
||||
output = File.read(path + '/../mock/reporters/json_min_output')
|
||||
output = JSON.parse(output, symbolize_names: true)
|
||||
report.report.must_equal output
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -13,9 +13,17 @@ describe Inspec::Reporters::Json do
|
|||
|
||||
describe '#render' do
|
||||
it 'confirm render output' do
|
||||
cli_output = File.read(path + '/../mock/reporters/json_output')
|
||||
output = File.read(path + '/../mock/reporters/json_output')
|
||||
report.render
|
||||
report.rendered_output.must_equal cli_output
|
||||
report.rendered_output.must_equal output
|
||||
end
|
||||
end
|
||||
|
||||
describe '#report' do
|
||||
it 'confirm report output' do
|
||||
output = File.read(path + '/../mock/reporters/json_output')
|
||||
output = JSON.parse(output, symbolize_names: true)
|
||||
report.report.must_equal output
|
||||
end
|
||||
end
|
||||
|
||||
|
|
Loading…
Reference in a new issue