mirror of
https://github.com/inspec/inspec
synced 2024-11-30 00:20:28 +00:00
CFINSPEC-374 DSL keyword only_applicable_if
added (#6229)
* Not applicable if logic addition Signed-off-by: Nikita Mathur <nikita.mathur@chef.io> * Changes from not applicable if to only applicable if Signed-off-by: Nikita Mathur <nikita.mathur@chef.io> * Fix to enable placing only_applicable_if at any position in control and for keeping impact zero intact Signed-off-by: Nikita Mathur <nikita.mathur@chef.io> * Doc change added for only_applicable_if Signed-off-by: Nikita Mathur <nikita.mathur@chef.io> * Doc Review Signed-off-by: Deepa Kumaraswamy <dkumaras@progress.com> * Added generic examples that supports cross platform for testing Signed-off-by: Nikita Mathur <nikita.mathur@chef.io> * Yet another build fix due to changes in test Signed-off-by: Nikita Mathur <nikita.mathur@chef.io> Signed-off-by: Nikita Mathur <nikita.mathur@chef.io> Signed-off-by: Deepa Kumaraswamy <dkumaras@progress.com> Co-authored-by: Deepa Kumaraswamy <dkumaras@progress.com>
This commit is contained in:
parent
60c80527f3
commit
f2dc49f6ea
8 changed files with 143 additions and 22 deletions
|
@ -325,6 +325,53 @@ end
|
|||
end
|
||||
```
|
||||
|
||||
### Use **only_applicable_if** to test controls for applicability
|
||||
|
||||
The `only_applicable_if` block allows to test if a control is applicable or not. In this example, the control with `only_applicable_if` block checks the condition and marks the control as not applicable (N/A) if the results of the `only_applicable_if` block evaluates to `false`.
|
||||
|
||||
If **gnome-desktop** is not installed, the following control to test gnome settings marks control as **not applicable**.
|
||||
|
||||
```ruby
|
||||
control 'gnome-destkop-settings' do
|
||||
impact 0.5
|
||||
desc 'some good settings'
|
||||
desc 'check', 'check the settings file for good things'
|
||||
desc 'fix', 'set the good things in the file /etc/gnome/settings'
|
||||
tag nist: 'CM-6'
|
||||
|
||||
only_applicable_if("The Gnome Desktop is not installed, this control is Not Applicable") {
|
||||
package('gnome-desktop').installed?
|
||||
}
|
||||
|
||||
describe gnome_settings do
|
||||
it should_be set_well
|
||||
end
|
||||
end
|
||||
```
|
||||
|
||||
Run output:
|
||||
|
||||
```bash
|
||||
inspec exec path/to/audit-gnome-settings-profile --enhanced-outcomes
|
||||
|
||||
Profile: InSpec Profile (audit-gnome-settings-profile)
|
||||
Version: 0.1.0
|
||||
Target: local://
|
||||
Target ID: fa3923b9-f806-4cc2-960d-1ddefb4c7654
|
||||
|
||||
N/A gnome-destkop-settings: No-op
|
||||
× No-op
|
||||
N/A control due to only_applicable_if condition: The Gnome Desktop is not installed, this control is Not Applicable
|
||||
|
||||
Profile Summary: 0 successful controls, 0 control failure, 0 controls not reviewed, 1 controls not applicable, 0 controls have error
|
||||
Test Summary: 0 successful, 1 failures, 0 skipped
|
||||
```
|
||||
|
||||
Some notes about `only_applicable_if`:
|
||||
|
||||
* `only_applicable_if` applies to the entire `control`. If the results of the `only_applicable_if` block evaluates to `false`, any Chef InSpec resources mentioned as part of a `describe` block will not be run. Additionally, the contents of the describe blocks will not be run.
|
||||
* If the results of the `only_applicable_if` block evaluates to `false`, it will invoke a failing test which will state the reason for N/A.
|
||||
|
||||
### Additional metadata for controls
|
||||
|
||||
The following example illustrates various ways to add tags and references to `control`
|
||||
|
|
|
@ -2,7 +2,8 @@ module Inspec
|
|||
module EnhancedOutcomes
|
||||
|
||||
def self.determine_status(results, impact)
|
||||
if results.any? { |r| !r[:exception].nil? && !r[:backtrace].nil? }
|
||||
# No-op exception occurs in case of not_applicable_if
|
||||
if results.any? { |r| !r[:exception].nil? && !r[:backtrace].nil? && r[:resource_class] != "noop" }
|
||||
"error"
|
||||
elsif !impact.nil? && impact.to_f == 0.0
|
||||
"not_applicable"
|
||||
|
|
|
@ -72,7 +72,7 @@ module Inspec::Plugin::V2::PluginType
|
|||
def control_has_error(notifications)
|
||||
notifications.any? do |notification_data|
|
||||
notification, _status = notification_data
|
||||
!notification.example.exception.nil? && !(notification.example.exception.is_a? RSpec::Expectations::ExpectationNotMetError) && !notification.example.exception.backtrace.nil?
|
||||
!notification.example.exception.nil? && !(notification.example.exception.is_a? RSpec::Expectations::ExpectationNotMetError) && !notification.example.exception.backtrace.nil? && (!notification.description.include? "No-op") # No-op exception occurs in case of not_applicable_if
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@ module Inspec
|
|||
include ::RSpec::Matchers
|
||||
|
||||
attr_reader :__waiver_data
|
||||
attr_accessor :resource_dsl
|
||||
attr_accessor :resource_dsl, :na_impact_freeze
|
||||
attr_reader :__profile_id
|
||||
|
||||
def initialize(id, profile_id, resource_dsl, opts, &block)
|
||||
|
@ -40,6 +40,7 @@ module Inspec
|
|||
@__merge_count = 0
|
||||
@__merge_changes = []
|
||||
@__skip_only_if_eval = opts[:skip_only_if_eval]
|
||||
@__na_rule = {}
|
||||
|
||||
# evaluate the given definition
|
||||
return unless block_given?
|
||||
|
@ -75,10 +76,13 @@ module Inspec
|
|||
end
|
||||
|
||||
def impact(v = nil)
|
||||
if v.is_a?(String)
|
||||
@impact = Inspec::Impact.impact_from_string(v)
|
||||
elsif !v.nil?
|
||||
@impact = v
|
||||
# N/A impact freeze is required when only_applicable_if block has reset impact value to zero"
|
||||
unless na_impact_freeze
|
||||
if v.is_a?(String)
|
||||
@impact = Inspec::Impact.impact_from_string(v)
|
||||
elsif !v.nil?
|
||||
@impact = v
|
||||
end
|
||||
end
|
||||
|
||||
@impact
|
||||
|
@ -145,6 +149,18 @@ module Inspec
|
|||
@__skip_rule[:message] = message
|
||||
end
|
||||
|
||||
def only_applicable_if(message = nil)
|
||||
return unless block_given?
|
||||
return if yield
|
||||
|
||||
impact(0.0)
|
||||
self.na_impact_freeze = true # this flag prevents impact value to reset to any other value
|
||||
|
||||
@__na_rule[:result] ||= !yield
|
||||
@__na_rule[:type] = :only_applicable_if
|
||||
@__na_rule[:message] = message
|
||||
end
|
||||
|
||||
# Describe will add one or more tests to this control. There is 2 ways
|
||||
# of calling it:
|
||||
#
|
||||
|
@ -255,6 +271,10 @@ module Inspec
|
|||
rule.instance_variable_get(:@__skip_rule)
|
||||
end
|
||||
|
||||
def self.na_status(rule)
|
||||
rule.instance_variable_get(:@__na_rule)
|
||||
end
|
||||
|
||||
def self.set_skip_rule(rule, value, message = nil, type = :only_if)
|
||||
rule.instance_variable_set(:@__skip_rule,
|
||||
{
|
||||
|
@ -276,16 +296,26 @@ module Inspec
|
|||
# creates a dummay array of "checks" with a skip outcome
|
||||
def self.prepare_checks(rule)
|
||||
skip_check = skip_status(rule)
|
||||
return checks(rule) unless skip_check[:result].eql?(true)
|
||||
|
||||
if skip_check[:message]
|
||||
msg = "Skipped control due to #{skip_check[:type]} condition: #{skip_check[:message]}"
|
||||
else
|
||||
msg = "Skipped control due to #{skip_check[:type]} condition."
|
||||
end
|
||||
na_check = na_status(rule)
|
||||
return checks(rule) unless skip_check[:result].eql?(true) || na_check[:result].eql?(true)
|
||||
|
||||
resource = rule.noop
|
||||
resource.skip_resource(msg)
|
||||
if skip_check[:result].eql?(true)
|
||||
if skip_check[:message]
|
||||
msg = "Skipped control due to #{skip_check[:type]} condition: #{skip_check[:message]}"
|
||||
else
|
||||
msg = "Skipped control due to #{skip_check[:type]} condition."
|
||||
end
|
||||
resource.skip_resource(msg)
|
||||
else
|
||||
if na_check[:message]
|
||||
msg = "N/A control due to #{na_check[:type]} condition: #{na_check[:message]}"
|
||||
else
|
||||
msg = "N/A control due to #{na_check[:type]} condition."
|
||||
end
|
||||
resource.fail_resource(msg)
|
||||
end
|
||||
|
||||
[["describe", [resource], nil]]
|
||||
end
|
||||
|
||||
|
|
|
@ -83,6 +83,12 @@ module InspecPlugins::StreamingReporterProgressBar
|
|||
control_id = notification.example.metadata[:id]
|
||||
title = notification.example.metadata[:title]
|
||||
full_description = notification.example.metadata[:full_description]
|
||||
|
||||
# No-op exception occurs in case of not_applicable_if
|
||||
if (full_description.include? "No-op") && notification.example.exception
|
||||
full_description += notification.example.exception.message
|
||||
end
|
||||
|
||||
set_status_mapping(control_id, status)
|
||||
collect_notifications(notification, control_id, status)
|
||||
control_ended = control_ended?(control_id)
|
||||
|
|
|
@ -65,15 +65,33 @@ end
|
|||
control "tmp-6.0.1" do
|
||||
impact 0.5
|
||||
only_if("Some reason for N/A", impact: 0.0) { false }
|
||||
describe file("/tmp") do
|
||||
it { should be_directory }
|
||||
describe "f.1" do
|
||||
it { should cmp "f.1" }
|
||||
end
|
||||
end
|
||||
|
||||
# Example of setting impact using code and not marked as N/A
|
||||
control "tmp-6.0.2" do
|
||||
only_if(impact: 0.5) { false }
|
||||
describe file("/tmp") do
|
||||
it { should be_directory }
|
||||
describe "f.2" do
|
||||
it { should cmp "f.2" }
|
||||
end
|
||||
end
|
||||
|
||||
# Example of setting impact using code and marking it N/A
|
||||
control "tmp-7.0.1" do
|
||||
only_applicable_if("Some reason for N/A") { false }
|
||||
impact 0.5
|
||||
describe "g.1" do
|
||||
it { should cmp "g.1" }
|
||||
end
|
||||
end
|
||||
|
||||
# Example of setting impact using code and not marking it N/A
|
||||
control "tmp-7.0.2" do
|
||||
only_applicable_if("Some reason for N/A") { true }
|
||||
impact 0.5
|
||||
describe "g.2" do
|
||||
it { should cmp "g.2" }
|
||||
end
|
||||
end
|
||||
|
|
|
@ -88,6 +88,8 @@ describe "inspec exec with streaming progress bar reporter" do
|
|||
_(out.stderr).must_include "[N/R] tmp-3.0.2"
|
||||
_(out.stderr).must_include "[FAILED] tmp-4.0"
|
||||
_(out.stderr).must_include "[PASSED] tmp-5.0"
|
||||
_(out.stderr).must_include "[N/A] tmp-7.0.1 No-op N/A control due to only_applicable_if condition: Some reason for N/A"
|
||||
_(out.stderr).must_include "[PASSED] tmp-7.0.2"
|
||||
assert_exit_code 100, out
|
||||
end
|
||||
|
||||
|
|
|
@ -1347,12 +1347,12 @@ EOT
|
|||
|
||||
it "should show enhanced_outcomes for controls with impact 0" do
|
||||
_(run_result.stdout).must_include "5 skipped"
|
||||
_(run_result.stdout).must_include "3 controls not applicable"
|
||||
_(run_result.stdout).must_include "4 controls not applicable"
|
||||
_(run_result.stdout).must_include "N/A"
|
||||
end
|
||||
|
||||
it "should show enhanced_outcomes for controls with errors" do
|
||||
_(run_result.stdout).must_include "3 failures"
|
||||
_(run_result.stdout).must_include "4 failures"
|
||||
_(run_result.stdout).must_include "2 controls have error"
|
||||
_(run_result.stdout).must_include "ERR"
|
||||
end
|
||||
|
@ -1362,7 +1362,7 @@ EOT
|
|||
end
|
||||
|
||||
it "should show enhanced_outcomes for passed controls" do
|
||||
_(run_result.stdout).must_include "1 successful control"
|
||||
_(run_result.stdout).must_include "2 successful control"
|
||||
end
|
||||
|
||||
it "should mark control as N/A using zero impact from only_if" do
|
||||
|
@ -1381,6 +1381,23 @@ EOT
|
|||
_(run_result.stdout).must_include "N/R tmp-6.0.2"
|
||||
end
|
||||
end
|
||||
|
||||
it "should mark control as N/A using only_applicable_if when false" do
|
||||
if windows?
|
||||
_(run_result.stdout).must_include "[N/A] tmp-7.0.1"
|
||||
else
|
||||
_(run_result.stdout).must_include "N/A tmp-7.0.1"
|
||||
end
|
||||
_(run_result.stdout).must_include "Some reason for N/A"
|
||||
end
|
||||
|
||||
it "should not mark control as N/A using only_applicable_if when true" do
|
||||
if windows?
|
||||
_(run_result.stdout).wont_include "[N/A] tmp-6.0.2"
|
||||
else
|
||||
_(run_result.stdout).wont_include "N/A tmp-6.0.2"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "when running profile with enhanced_outcomes option and yaml reporter" do
|
||||
|
|
Loading…
Reference in a new issue