Skip to content

Commit

Permalink
Merge pull request #219 from chef-cookbooks/chris-rock/remove-json-co…
Browse files Browse the repository at this point in the history
…nversion

stick to plain ruby hash
  • Loading branch information
arlimus authored May 3, 2017
2 parents d6bbb2a + 5176182 commit 78b2eff
Show file tree
Hide file tree
Showing 13 changed files with 283 additions and 284 deletions.
11 changes: 5 additions & 6 deletions files/default/handler/audit_report.rb
Original file line number Diff line number Diff line change
Expand Up @@ -144,16 +144,16 @@ def call(opts, profiles)
r.delete(:controls)

# calculate statistics
stats = count_controls(JSON.parse(r[:profiles].to_json))
stats = count_controls(r[:profiles])

time = 0
time = r[:statistics][:duration] unless r[:statistics].nil?

# count controls
Chef::Log.info "Summary #{stats['total']} controls: #{stats['passed']['total']} successful, #{stats['failed']['total']} failures, #{stats['skipped']['total']} skipped in #{time} s"
Chef::Log.info "Summary #{stats[:total]} controls: #{stats[:passed][:total]} successful, #{stats[:failed][:total]} failures, #{stats[:skipped][:total]} skipped in #{time} s"
end

r.to_json
Chef::Log.debug "Audit Report #{r}"
r
else
Chef::Log.warn 'No audit tests are defined.'
{}
Expand All @@ -179,13 +179,12 @@ def gather_nodeinfo
end

# send InSpec report to the reporter (see libraries/reporters.rb)
def send_report(reporter, server, user, source_location, content)
def send_report(reporter, server, user, source_location, report)
Chef::Log.info "Reporting to #{reporter}"

# Set `insecure` here to avoid passing 6 aruguments to `AuditReport#send_report`
# See `cookstyle` Metrics/ParameterLists
insecure = node['audit']['insecure']
report = JSON.parse(content)

# TODO: harmonize reporter interface
if reporter == 'chef-visibility' || reporter == 'chef-automate'
Expand Down
52 changes: 26 additions & 26 deletions libraries/helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -142,37 +142,37 @@ def get_reporters(audit)
# e.g. minor: 10, major: 15, critical: 25
def count_controls(profiles)
count = {
'total' => 0,
'passed' => {
'total' => 0,
total: 0,
passed: {
total: 0,
},
'skipped' => {
'total' => 0,
skipped: {
total: 0,
},
'failed' => {
'total' => 0,
'minor' => 0,
'major' => 0,
'critical' => 0,
failed: {
total: 0,
minor: 0,
major: 0,
critical: 0,
},
}
return count unless profiles.is_a?(Array)

profiles.each do |profile|
next unless profile && profile['controls'].is_a?(Array)
profile['controls'].each do |control|
count['total'] += 1
next unless profile && profile[:controls].is_a?(Array)
profile[:controls].each do |control|
count[:total] += 1
# ensure all impacts are float
control['impact'] = control['impact'].to_f
case control_status(control['results'])
control[:impact] = control[:impact].to_f
case control_status(control[:results])
when 'passed'
count['passed']['total'] += 1
count[:passed][:total] += 1
when 'skipped'
count['skipped']['total'] += 1
count[:skipped][:total] += 1
when 'failed'
count['failed']['total'] += 1
criticality = impact_to_s(control['impact'])
count['failed'][criticality] += 1 unless criticality.nil?
count[:failed][:total] += 1
criticality = impact_to_s(control[:impact])
count[:failed][criticality.to_sym] += 1 unless criticality.nil?
end
end
end
Expand All @@ -182,11 +182,11 @@ def count_controls(profiles)
# Returns a complince status string based on the passed/failed/skipped controls
def compliance_status(counts)
return 'unknown' unless counts.is_a?(Hash) &&
counts['failed'].is_a?(Hash) &&
counts['skipped'].is_a?(Hash)
if counts['failed']['total'] > 0
counts[:failed].is_a?(Hash) &&
counts[:skipped].is_a?(Hash)
if counts[:failed][:total] > 0
'failed'
elsif counts['total'] == counts['skipped']['total']
elsif counts[:total] == counts[:skipped][:total]
'skipped'
else
'passed'
Expand All @@ -210,8 +210,8 @@ def control_status(results)
return unless results.is_a?(Array)
status = 'passed'
results.each do |result|
return 'failed' if result['status'] == 'failed'
status = 'skipped' if result['status'] == 'skipped'
return 'failed' if result[:status] == 'failed'
status = 'skipped' if result[:status] == 'skipped'
end
status
end
Expand Down
62 changes: 31 additions & 31 deletions libraries/reporters/automate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def send_report(report)
return false
end

json_report = enriched_report(report)
json_report = enriched_report(report).to_json

unless json_report
Chef::Log.warn 'Something went wrong, report can\'t be nil'
Expand Down Expand Up @@ -75,27 +75,27 @@ def send_report(report)

# Some document stores like ElasticSearch don't like values that change type
# This function converts all profile attribute defaults to string and
# adds a 'type' key to store the original type
# adds a :type key to store the original type
def typed_attributes(profiles)
return profiles unless profiles.class == Array && !profiles.empty?
profiles.each { |profile|
next unless profile['attributes'].class == Array && !profile['attributes'].empty?
profile['attributes'].map { |attrib|
case attrib['options']['default'].class.to_s
next unless profile[:attributes].class == Array && !profile[:attributes].empty?
profile[:attributes].map { |attrib|
case attrib[:options][:default].class.to_s
when 'String'
attrib['options']['type'] = 'string'
attrib[:options][:type] = 'string'
when 'FalseClass'
attrib['options']['type'] = 'boolean'
attrib['options']['default'] = attrib['options']['default'].to_s
attrib[:options][:type] = 'boolean'
attrib[:options][:default] = attrib[:options][:default].to_s
when 'Fixnum'
attrib['options']['type'] = 'int'
attrib['options']['default'] = attrib['options']['default'].to_s
attrib[:options][:type] = 'int'
attrib[:options][:default] = attrib[:options][:default].to_s
when 'Float'
attrib['options']['type'] = 'float'
attrib['options']['default'] = attrib['options']['default'].to_s
attrib[:options][:type] = 'float'
attrib[:options][:default] = attrib[:options][:default].to_s
else
Chef::Log.warn "enriched_report: unsupported data type(#{attrib['options']['default'].class}) for attribute #{attrib['options']['name']}"
attrib['options']['type'] = 'unknown'
Chef::Log.warn "enriched_report: unsupported data type(#{attrib[:options][:default].class}) for attribute #{attrib[:options]['name']}"
attrib[:options][:type] = 'unknown'
end
}
}
Expand All @@ -109,31 +109,31 @@ def typed_attributes(profiles)
def enriched_report(content)
return nil unless content.is_a?(Hash)
final_report = {}
total_duration = content['statistics']['duration']
inspec_version = content['version']
total_duration = content[:statistics][:duration]
inspec_version = content[:version]

# strip the report to leave only the profiles
final_report['profiles'] = content['profiles']
final_report[:profiles] = content[:profiles]

# remove nil profiles if any
final_report['profiles'].select! { |p| p }
final_report[:profiles].select! { |p| p }

# set types for profile attributes
final_report['profiles'] = typed_attributes(final_report['profiles'])
final_report[:profiles] = typed_attributes(final_report[:profiles])

# add some additional fields to ease report parsing
final_report['event_type'] = 'inspec'
final_report['event_action'] = 'exec'
final_report['compliance_summary'] = count_controls(final_report['profiles'])
final_report['compliance_summary']['status'] = compliance_status(final_report['compliance_summary'])
final_report['compliance_summary']['node_name'] = @node_name
final_report['compliance_summary']['end_time'] = DateTime.now.iso8601
final_report['compliance_summary']['duration'] = total_duration
final_report['compliance_summary']['inspec_version'] = inspec_version
final_report['entity_uuid'] = @entity_uuid
final_report['run_id'] = @run_id
Chef::Log.info "Compliance Summary #{final_report['compliance_summary']}"
final_report.to_json
final_report[:event_type] = 'inspec'
final_report[:event_action] = 'exec'
final_report[:compliance_summary] = count_controls(final_report[:profiles])
final_report[:compliance_summary][:status] = compliance_status(final_report[:compliance_summary])
final_report[:compliance_summary][:node_name] = @node_name
final_report[:compliance_summary][:end_time] = DateTime.now.iso8601
final_report[:compliance_summary][:duration] = total_duration
final_report[:compliance_summary][:inspec_version] = inspec_version
final_report[:entity_uuid] = @entity_uuid
final_report[:run_id] = @run_id
Chef::Log.info "Compliance Summary #{final_report[:compliance_summary]}"
final_report
end
end
end
32 changes: 15 additions & 17 deletions libraries/reporters/compliance.rb
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ def send_report(report)

min_report = transform(report)
json_report = enriched_report(min_report, @source_location)
req.body = json_report
req.body = json_report.to_json

# TODO: use secure option
uri = URI(@url)
Expand All @@ -50,7 +50,7 @@ def enriched_report(report, source_location)
blob = @node_info.dup

# extract profile names
profiles = report['controls'].collect { |control| control['profile_id'] }.uniq
profiles = report[:controls].collect { |control| control[:profile_id] }.uniq

# build report for chef compliance, it includes node data
blob[:reports] = {}
Expand All @@ -62,41 +62,39 @@ def enriched_report(report, source_location)
namespace = compliance_profiles.select { |entry| entry[:profile_id] == profile }
unless namespace.nil? && namespace.empty?
Chef::Log.debug "Namespace for #{profile} is #{namespace[0][:owner]}"
blob[:profiles][profile] = namespace[0][:owner]
blob[:reports][profile] = report.dup
blob[:profiles][profile.to_sym] = namespace[0][:owner]
blob[:reports][profile.to_sym] = report.dup
# filter controls by profile_id
blob[:reports][profile]['controls'] = blob[:reports][profile]['controls'].select { |control| control['profile_id'] == profile }
blob[:reports][profile.to_sym][:controls] = blob[:reports][profile.to_sym][:controls].select { |control| control[:profile_id] == profile }
else
Chef::Log.warn "Could not determine compliance namespace for #{profile}"
end
}

blob.to_json
blob
end

# transforms a full InSpec json report to a min InSpec json report
def transform(full_report)
min_report = {}
min_report['version'] = full_report[:version]
min_report[:version] = full_report[:version]

# iterate over each profile and control
min_report['controls'] = []
min_report[:controls] = []
full_report[:profiles].each { |profile|
min_report['controls'] += profile[:controls].map { |control|
min_report[:controls] += profile[:controls].map { |control|
control[:results].map { |result|
c = {}
c['id'] = control[:id]
c['profile_id'] = profile[:name]
c['status'] = result[:status]
c['code_desc'] = result[:code_desc]
c[:id] = control[:id]
c[:profile_id] = profile[:name]
c[:status] = result[:status]
c[:code_desc] = result[:code_desc]
c
}
}
}
min_report['controls'].flatten!
min_report['statistics'] = {
'duration' => full_report[:statistics][:duration],
}
min_report[:controls].flatten!
min_report[:statistics] = full_report[:statistics]
min_report
end

Expand Down
4 changes: 2 additions & 2 deletions libraries/reporters/cs_automate.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def initialize(opts)
end

def send_report(report)
json_report = enriched_report(report)
automate_report = enriched_report(report)

if @insecure
Chef::Config[:verify_api_cert] = false
Expand All @@ -27,7 +27,7 @@ def send_report(report)
Chef::Log.info "Report to Chef Automate via Chef Server: #{@url}"
rest = Chef::ServerAPI.new(@url, Chef::Config)
with_http_rescue do
rest.post(@url, JSON.parse(json_report))
rest.post(@url, automate_report)
return true
end
false
Expand Down
4 changes: 2 additions & 2 deletions libraries/reporters/cs_compliance.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ module Reporter
class ChefServerCompliance < ChefCompliance
def send_report(report)
min_report = transform(report)
json_report = enriched_report(min_report, @source_location)
cc_report = enriched_report(min_report, @source_location)

# TODO: only disable if insecure option is set
Chef::Config[:verify_api_cert] = false
Expand All @@ -19,7 +19,7 @@ def send_report(report)
Chef::Log.info "Report to Chef Compliance via Chef Server: #{@url}"
rest = Chef::ServerAPI.new(@url, Chef::Config)
with_http_rescue do
rest.post(@url, JSON.parse(json_report))
rest.post(@url, cc_report)
return true
end
false
Expand Down
Loading

0 comments on commit 78b2eff

Please sign in to comment.