def check # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
Returns:
-
(Boolean)
- true if no errors were found, false otherwise
def check # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
# initial values for response object
result = {
summary: {
valid: false,
timestamp: Time.now.iso8601,
location: @target,
profile: nil,
controls: 0,
},
errors: [],
warnings: [],
offenses: [],
}
entry = lambda { |file, line, column, control, msg|
{
file: file,
line: line,
column: column,
control_id: control,
msg: msg,
}
}
warn = lambda { |file, line, column, control, msg|
@logger.warn(msg)
result[:warnings].push(entry.call(file, line, column, control, msg))
}
error = lambda { |file, line, column, control, msg|
@logger.error(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}"
meta_path = @source_reader.target.abs_path(@source_reader.metadata.ref)
# verify metadata
m_errors, m_warnings = metadata.valid
m_errors.each { |msg| error.call(meta_path, 0, 0, nil, msg) }
m_warnings.each { |msg| warn.call(meta_path, 0, 0, nil, msg) }
m_unsupported = metadata.unsupported
m_unsupported.each { |u| warn.call(meta_path, 0, 0, nil, "doesn't support: #{u}") }
@logger.info "Metadata OK." if m_errors.empty? && m_unsupported.empty?
# only run the vendor check if the legacy profile-path is not used as argument
if @legacy_profile_path == false
# verify that a lockfile is present if we have dependencies
unless metadata.dependencies.empty?
error.call(meta_path, 0, 0, nil, "Your profile needs to be vendored with `inspec vendor`.") unless lockfile_exists?
end
if lockfile_exists?
# verify if metadata and lockfile are out of sync
if lockfile.deps.size != metadata.dependencies.size
error.call(meta_path, 0, 0, nil, "inspec.yml and inspec.lock are out-of-sync. Please re-vendor with `inspec vendor`.")
end
# verify if metadata and lockfile have the same dependency names
metadata.dependencies.each do |dep|
# Skip if the dependency does not specify a name
next if dep[:name].nil?
# TODO: should we also verify that the soure is the same?
unless lockfile.deps.map { |x| x[:name] }.include? dep[:name]
error.call(meta_path, 0, 0, nil, "Cannot find #{dep[:name]} in lockfile. Please re-vendor with `inspec vendor`.")
end
end
end
end
# extract profile name
result[:summary][:profile] = metadata.params[:name]
count = controls_count
result[:summary][:controls] = count
if count == 0
warn.call(nil, nil, nil, nil, "No controls or tests were defined.")
else
@logger.info("Found #{count} controls.")
end
# iterate over hash of groups
params[:controls].each do |id, control|
sfile = control[:source_location][:ref]
sline = control[:source_location][:line]
error.call(sfile, sline, nil, id, "Avoid controls with empty IDs") if id.nil? || id.empty?
next if id.start_with? "(generated "
warn.call(sfile, sline, nil, id, "Control #{id} has no title") if control[:title].to_s.empty?
warn.call(sfile, sline, nil, id, "Control #{id} has no descriptions") if control[:descriptions][:default].to_s.empty?
warn.call(sfile, sline, nil, id, "Control #{id} has impact > 1.0") if control[:impact].to_f > 1.0
warn.call(sfile, sline, nil, id, "Control #{id} has impact < 0.0") if control[:impact].to_f < 0.0
warn.call(sfile, sline, nil, id, "Control #{id} has no tests defined") if control[:checks].nil? || control[:checks].empty?
end
# Running cookstyle to check for code offenses
if @check_cookstyle
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
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?
result
end