Add a merged json report for A2 (#3261)

* Provide a json_merge report used by A2 that merges all child profiles.

Signed-off-by: Jared Quick <jquick@chef.io>

* Merge profile controls from child up until we find something usable.

Signed-off-by: Jared Quick <jquick@chef.io>

* Add testng for json_merged report.

Signed-off-by: Jared Quick <jquick@chef.io>

* Push the profile population to be later in the report.

Signed-off-by: Jared Quick <jquick@chef.io>
This commit is contained in:
Jared Quick 2018-08-03 10:07:01 -04:00 committed by Christoph Hartmann
parent 0068683601
commit 73a40139a6
6 changed files with 141 additions and 2 deletions

View file

@ -1,6 +1,7 @@
require 'inspec/reporters/base'
require 'inspec/reporters/cli'
require 'inspec/reporters/json'
require 'inspec/reporters/json_merged'
require 'inspec/reporters/json_min'
require 'inspec/reporters/junit'
require 'inspec/reporters/automate'

View file

@ -4,7 +4,7 @@ require 'json'
require 'net/http'
module Inspec::Reporters
class Automate < Json
class Automate < JsonMerged
def initialize(config)
super(config)
@ -17,7 +17,7 @@ module Inspec::Reporters
def enriched_report
# grab the report from the parent class
final_report = report
final_report = report_merged
# Label this content as an inspec_report
final_report[:type] = 'inspec_report'

View file

@ -0,0 +1,73 @@
# encoding: utf-8
require 'json'
module Inspec::Reporters
class JsonMerged < Json
def initialize(config)
super(config)
@profiles = []
end
def render
output(report_merged.to_json, false)
end
def report_merged
# grab profiles from the json parent class
@profiles = report[:profiles]
{
platform: platform,
profiles: merge_profiles,
statistics: {
duration: run_data[:statistics][:duration],
},
version: run_data[:version],
}
end
private
def merge_profiles
@profiles.each do |profile|
next unless profile.key?(:parent_profile)
parent_profile = find_master_parent(profile)
merge_controls(parent_profile, profile)
merge_depends(parent_profile, profile)
end
# delete child profiles
@profiles.delete_if { |p| p.key?(:parent_profile) }
@profiles
end
def find_master_parent(profile)
return profile unless profile.key?(:parent_profile)
parent_profile = @profiles.select { |parent| parent[:name] == profile[:parent_profile] }.first
find_master_parent(parent_profile)
end
def merge_controls(parent, child)
parent[:controls].each do |control|
child_control = child[:controls].select { |c| c[:id] == control[:id] }.first
next if child_control.nil?
control.each do |name, _value|
child_value = child_control[name]
next if child_value.nil? || (child_value.respond_to?(:empty?) && child_value.empty?)
control[name] = child_value
end
end
end
def merge_depends(parent, child)
return unless child.key?(:depends)
child[:depends].each do |d|
parent[:depends] << d
end
end
end
end

View file

@ -0,0 +1 @@
{"platform":{"name":"mac_os_x","release":"17.5.0"},"profiles":[{"name":"wrapper-override","version":"0.6.1","sha256":"7436aac31d44de7987419d5f2ffb822f265645f4fc3c5d2ab37d8fff4dd5cf61","title":"Linux Wrapper Child Profile","maintainer":"Demo, Inc.","summary":"Profile that wraps other profiles","license":"Apache-2.0","copyright":"Demo, Inc.","copyright_email":"support@example.com","supports":[],"attributes":[],"depends":[{"name":"myprofile1z","url":"https://s3-eu-west-1.amazonaws.com/apop-bucket/profiles/myprofile1-1.0.0.tar.gz"}],"groups":[{"id":"/Users/jquick/.inspec/cache/e39eb85366b272bae98e5eecdfac9f84c50a9ae9dd625fba2ce847268a6c3477/controls/profile1.rb","controls":["pro1-con1","pro1-con2","pro1-con4"]}],"controls":[{"id":"pro1-con1","title":"Profile 1 - Control 1","desc":"Profile 1 - Control 1 description","impact":0.8,"refs":[],"tags":{"hosts":null,"file":null,"cce":"CCE-27072-8"},"code":"control 'pro1-con1' do\n impact 0.8\n title 'Profile 1 - Control 1'\n desc 'Profile 1 - Control 1 description'\n tag 'hosts','file'\n tag cce: 'CCE-27072-8'\n describe file('/etc/hosts') do\n its('mode') { should eq 0644 }\n end\nend\n","source_location":{"line":1,"ref":"/Users/jquick/.inspec/cache/e39eb85366b272bae98e5eecdfac9f84c50a9ae9dd625fba2ce847268a6c3477/controls/profile1.rb"},"results":[{"status":"passed","code_desc":"File /etc/hosts mode should eq 420","run_time":0.031503,"start_time":"2018-07-30T08:56:41-04:00"}]},{"id":"pro1-con2","title":"Profile 1 - Control 2-updated","desc":"Profile 1 - Control 2 description-updated","impact":0.999,"refs":[{"ref":[{"url":"https://example.com","ref":"Section 3.5.2.1"}]}],"tags":{"password":null,"password-updated":null},"code":" control 'pro1-con2' do\n impact 0.999\n title 'Profile 1 - Control 2-updated'\n desc 'Profile 1 - Control 2 description-updated'\n tag 'password-updated'\n ref 'Section 3.5.2.1', url: 'https://example.com'\n describe file('/etc/passwd') do\n it { should exist }\n end\n end\n","source_location":{"line":6,"ref":"wrapper-override/controls/defaut.rb"},"results":[{"status":"passed","code_desc":"File /etc/passwd should exist","run_time":0.003954,"start_time":"2018-07-30T08:56:41-04:00"}]},{"id":"pro1-con4","title":"Profile 1 - Control 3 - useless","desc":"Profile 1 - Control 3 description","impact":1,"refs":[],"tags":{},"code":"control 'pro1-con4' do\n impact 1\n title 'Profile 1 - Control 3 - useless'\n desc 'Profile 1 - Control 3 description'\n only_if do\n 1.eql?(0)\n end\n describe file('/tmp5') do\n it { should exist }\n end\nend\n","source_location":{"line":31,"ref":"/Users/jquick/.inspec/cache/e39eb85366b272bae98e5eecdfac9f84c50a9ae9dd625fba2ce847268a6c3477/controls/profile1.rb"},"results":[{"status":"skipped","code_desc":"Operating System Detection","run_time":2.9e-05,"start_time":"2018-07-30T08:56:41-04:00","resource":"Operating System Detection","skip_message":"Skipped control due to only_if condition."}]}]}],"statistics":{"duration":0.039182},"version":"2.2.26"}

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,63 @@
# encoding: utf-8
require 'helper'
describe Inspec::Reporters::JsonMin do
let(:path) { File.expand_path(File.dirname(__FILE__)) }
let(:report) do
data = JSON.parse(File.read(path + '/../mock/reporters/run_data_wrapper.json'), symbolize_names: true)
Inspec::Reporters::JsonMerged.new({ run_data: data })
end
let(:profiles) { report.report[:profiles] }
describe '#render' do
it 'confirms render output' do
output = File.read(path + '/../mock/reporters/json_merged_output')
report.render
report.rendered_output.must_equal output
end
end
describe '#report_merged' do
it 'outputs the correct report_merged' do
output = File.read(path + '/../mock/reporters/json_merged_output')
output = JSON.parse(output, symbolize_names: true)
report.report_merged.must_equal output
end
end
describe '#find_master_parent' do
it 'finds the parent' do
report.instance_variable_set(:@profiles, profiles)
parent = report.send(:find_master_parent, profiles[1])
parent[:name].must_equal 'wrapper-override'
end
end
describe '#merge_controls' do
it 'merges profile controls' do
parent = profiles[0]
child = profiles[1]
parent[:controls].select { |c| c[:id] == 'pro1-con4' }.first[:code].must_equal ''
report.send(:merge_controls, parent, child)
assert = "control 'pro1-con4' do\n impact 1\n title 'Profile 1 - Control 3 - useless'\n desc 'Profile 1 - Control 3 description'\n only_if do\n 1.eql?(0)\n end\n describe file('/tmp5') do\n it { should exist }\n end\nend\n"
parent[:controls].select { |c| c[:id] == 'pro1-con4' }.first[:code].must_equal assert
end
end
describe '#merge_depends' do
it 'merges profile depends' do
parent = profiles[0]
child = profiles[1]
child[:depends] = [{:name=>"myprofile2", :url=>"https://test/myprofile2-1.0.0.tar.gz"}]
assert = [{:name=>"myprofile1z", :url=>"https://s3-eu-west-1.amazonaws.com/apop-bucket/profiles/myprofile1-1.0.0.tar.gz"}]
parent[:depends].must_equal assert
report.send(:merge_depends, parent, child)
assert = [
{:name=>"myprofile1z", :url=>"https://s3-eu-west-1.amazonaws.com/apop-bucket/profiles/myprofile1-1.0.0.tar.gz"},
{:name=>"myprofile2", :url=>"https://test/myprofile2-1.0.0.tar.gz"},
]
parent[:depends].must_equal assert
end
end
end