summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTim Smith <tsmith@chef.io>2021-11-16 17:15:18 -0800
committerGitHub <noreply@github.com>2021-11-16 17:15:18 -0800
commitb7c1d00d0c949c1cd811d07e80534a6da3b7b369 (patch)
treecaaf7c7754939b6e77186447ece73f54c39d1a1c
parentc004ed06c393c7793b936ad6cba1afde96e08d66 (diff)
parent798b0a1264ad6fe15181023f3c55a48e71170673 (diff)
downloadchef-b7c1d00d0c949c1cd811d07e80534a6da3b7b369.tar.gz
Merge pull request #12226 from chef/lcg/compliance-interval-runs
Implement compliance phase interval runs
-rw-r--r--lib/chef/compliance/default_attributes.rb12
-rw-r--r--lib/chef/compliance/runner.rb48
-rw-r--r--spec/unit/compliance/runner_spec.rb60
3 files changed, 116 insertions, 4 deletions
diff --git a/lib/chef/compliance/default_attributes.rb b/lib/chef/compliance/default_attributes.rb
index 3ecb1cd056..fc30716205 100644
--- a/lib/chef/compliance/default_attributes.rb
+++ b/lib/chef/compliance/default_attributes.rb
@@ -94,7 +94,17 @@ class Chef
# Should the built-in compliance phase run. True and false force the behavior. Nil does magic based on if you have
# profiles defined but do not have the audit cookbook enabled.
- "compliance_phase" => false
+ "compliance_phase" => false,
+
+ "interval" => {
+ # control how often inspec scans are run, if not on every node converge
+ # notes: false value will result in running inspec scan every converge
+ "enabled" => false,
+
+ # controls how often inspec scans are run (in minutes)
+ # notes: only used if interval is enabled above
+ "time" => 1440,
+ }
)
end
end
diff --git a/lib/chef/compliance/runner.rb b/lib/chef/compliance/runner.rb
index ade35d4861..f6d3e89b15 100644
--- a/lib/chef/compliance/runner.rb
+++ b/lib/chef/compliance/runner.rb
@@ -71,7 +71,7 @@ class Chef
logger.debug("#{self.class}##{__method__}: enabling Compliance Phase")
- report
+ report_with_interval
end
def run_failed(_exception, _run_status)
@@ -82,7 +82,7 @@ class Chef
logger.debug("#{self.class}##{__method__}: enabling Compliance Phase")
- report
+ report_with_interval
end
### Below code adapted from audit cookbook's files/default/handler/audit_report.rb
@@ -92,7 +92,6 @@ class Chef
fail_if_not_present
inspec_gem_source
inspec_version
- interval
owner
raise_if_unreachable
}.freeze
@@ -106,6 +105,15 @@ class Chef
end
end
+ def report_with_interval
+ if interval_seconds_left <= 0
+ create_timestamp_file if interval_enabled
+ report
+ else
+ logger.info "Skipping Chef Infra Compliance Phase due to interval settings (next run in #{interval_seconds_left / 60.0} mins)"
+ end
+ end
+
def report(report = nil)
logger.info "Starting Chef Infra Compliance Phase"
report ||= generate_report
@@ -362,6 +370,40 @@ class Chef
def requested_reporters
(Array(node["audit"]["reporter"]) + ["cli"]).uniq
end
+
+ def create_timestamp_file
+ FileUtils.touch report_timing_file
+ end
+
+ def report_timing_file
+ ::File.join(Chef::FileCache.create_cache_path("compliance"), "report_timing.json")
+ end
+
+ def interval_time
+ @interval_time ||= node.read("audit", "interval", "time")
+ end
+
+ def interval_enabled
+ @interval_enabled ||= node.read("audit", "interval", "enabled")
+ end
+
+ def interval_seconds
+ @interval_seconds ||=
+ if interval_enabled
+ logger.debug "Running Chef Infra Compliance Phase every #{interval_time} minutes"
+ interval_time * 60
+ else
+ logger.debug "Running Chef Infra Compliance Phase on every run"
+ 0
+ end
+ end
+
+ def interval_seconds_left
+ return 0 unless ::File.exist?(report_timing_file)
+
+ seconds_since_last_run = Time.now - ::File.mtime(report_timing_file)
+ interval_seconds - seconds_since_last_run
+ end
end
end
end
diff --git a/spec/unit/compliance/runner_spec.rb b/spec/unit/compliance/runner_spec.rb
index 602d675d4d..e8a08abfc1 100644
--- a/spec/unit/compliance/runner_spec.rb
+++ b/spec/unit/compliance/runner_spec.rb
@@ -1,4 +1,5 @@
require "spec_helper"
+require "tmpdir"
describe Chef::Compliance::Runner do
let(:logger) { double(:logger).as_null_object }
@@ -283,4 +284,63 @@ describe Chef::Compliance::Runner do
expect(inputs["chef_node"]["chef_environment"]).to eq("_default")
end
end
+
+ describe "interval running" do
+ let(:tempdir) { Dir.mktmpdir("chef-compliance-tests") }
+
+ before do
+ allow(runner).to receive(:report_timing_file).and_return("#{tempdir}/report_timing.json")
+ end
+
+ it "is disabled by default" do
+ expect(runner.node["audit"]["interval"]["enabled"]).to be false
+ end
+
+ it "defaults to 24 hours / 1440 minutes" do
+ expect(runner.node["audit"]["interval"]["time"]).to be 1440
+ end
+
+ it "runs when the timing file does not exist" do
+ expect(runner).to receive(:report)
+ runner.report_with_interval
+ end
+
+ it "runs when the timing file does not exist and intervals are enabled" do
+ node.normal["audit"]["interval"]["enabled"] = true
+ expect(runner).to receive(:report)
+ runner.report_with_interval
+ end
+
+ it "runs when the timing file exists and has a recent timestamp" do
+ FileUtils.touch runner.report_timing_file
+ expect(runner).to receive(:report)
+ runner.report_with_interval
+ end
+
+ it "does not runs when the timing file exists and has a recent timestamp and intervals are enabled" do
+ node.normal["audit"]["interval"]["enabled"] = true
+ FileUtils.touch runner.report_timing_file
+ expect(runner).not_to receive(:report)
+ runner.report_with_interval
+ end
+
+ it "does not runs when the timing file exists and has a recent timestamp and intervals are enabled" do
+ node.normal["audit"]["interval"]["enabled"] = true
+ FileUtils.touch runner.report_timing_file
+ ten_minutes_ago = Time.now - 600
+ File.utime ten_minutes_ago, ten_minutes_ago, runner.report_timing_file
+ expect(runner).not_to receive(:report)
+ runner.report_with_interval
+ end
+
+ it "runs when the timing file exists and has a recent timestamp and intervals are enabled and the time is short" do
+ node.normal["audit"]["interval"]["enabled"] = true
+ node.normal["audit"]["interval"]["time"] = 9
+ FileUtils.touch runner.report_timing_file
+ ten_minutes_ago = Time.now - 600
+ File.utime ten_minutes_ago, ten_minutes_ago, runner.report_timing_file
+ expect(runner).to receive(:report)
+ runner.report_with_interval
+ end
+ end
end