diff options
author | Tim Smith <tsmith@chef.io> | 2021-11-16 17:15:18 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-11-16 17:15:18 -0800 |
commit | b7c1d00d0c949c1cd811d07e80534a6da3b7b369 (patch) | |
tree | caaf7c7754939b6e77186447ece73f54c39d1a1c | |
parent | c004ed06c393c7793b936ad6cba1afde96e08d66 (diff) | |
parent | 798b0a1264ad6fe15181023f3c55a48e71170673 (diff) | |
download | chef-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.rb | 12 | ||||
-rw-r--r-- | lib/chef/compliance/runner.rb | 48 | ||||
-rw-r--r-- | spec/unit/compliance/runner_spec.rb | 60 |
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 |