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:
Nikita Mathur 2022-09-29 19:14:52 +05:30 committed by GitHub
parent 60c80527f3
commit f2dc49f6ea
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 143 additions and 22 deletions

View file

@ -325,6 +325,53 @@ end
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 ### Additional metadata for controls
The following example illustrates various ways to add tags and references to `control` The following example illustrates various ways to add tags and references to `control`

View file

@ -2,7 +2,8 @@ module Inspec
module EnhancedOutcomes module EnhancedOutcomes
def self.determine_status(results, impact) 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" "error"
elsif !impact.nil? && impact.to_f == 0.0 elsif !impact.nil? && impact.to_f == 0.0
"not_applicable" "not_applicable"

View file

@ -72,7 +72,7 @@ module Inspec::Plugin::V2::PluginType
def control_has_error(notifications) def control_has_error(notifications)
notifications.any? do |notification_data| notifications.any? do |notification_data|
notification, _status = 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
end end

View file

@ -16,7 +16,7 @@ module Inspec
include ::RSpec::Matchers include ::RSpec::Matchers
attr_reader :__waiver_data attr_reader :__waiver_data
attr_accessor :resource_dsl attr_accessor :resource_dsl, :na_impact_freeze
attr_reader :__profile_id attr_reader :__profile_id
def initialize(id, profile_id, resource_dsl, opts, &block) def initialize(id, profile_id, resource_dsl, opts, &block)
@ -40,6 +40,7 @@ module Inspec
@__merge_count = 0 @__merge_count = 0
@__merge_changes = [] @__merge_changes = []
@__skip_only_if_eval = opts[:skip_only_if_eval] @__skip_only_if_eval = opts[:skip_only_if_eval]
@__na_rule = {}
# evaluate the given definition # evaluate the given definition
return unless block_given? return unless block_given?
@ -75,10 +76,13 @@ module Inspec
end end
def impact(v = nil) def impact(v = nil)
if v.is_a?(String) # N/A impact freeze is required when only_applicable_if block has reset impact value to zero"
@impact = Inspec::Impact.impact_from_string(v) unless na_impact_freeze
elsif !v.nil? if v.is_a?(String)
@impact = v @impact = Inspec::Impact.impact_from_string(v)
elsif !v.nil?
@impact = v
end
end end
@impact @impact
@ -145,6 +149,18 @@ module Inspec
@__skip_rule[:message] = message @__skip_rule[:message] = message
end 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 # Describe will add one or more tests to this control. There is 2 ways
# of calling it: # of calling it:
# #
@ -255,6 +271,10 @@ module Inspec
rule.instance_variable_get(:@__skip_rule) rule.instance_variable_get(:@__skip_rule)
end 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) def self.set_skip_rule(rule, value, message = nil, type = :only_if)
rule.instance_variable_set(:@__skip_rule, rule.instance_variable_set(:@__skip_rule,
{ {
@ -276,16 +296,26 @@ module Inspec
# creates a dummay array of "checks" with a skip outcome # creates a dummay array of "checks" with a skip outcome
def self.prepare_checks(rule) def self.prepare_checks(rule)
skip_check = skip_status(rule) skip_check = skip_status(rule)
return checks(rule) unless skip_check[:result].eql?(true) na_check = na_status(rule)
return checks(rule) unless skip_check[:result].eql?(true) || na_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 = rule.noop 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]] [["describe", [resource], nil]]
end end

View file

@ -83,6 +83,12 @@ module InspecPlugins::StreamingReporterProgressBar
control_id = notification.example.metadata[:id] control_id = notification.example.metadata[:id]
title = notification.example.metadata[:title] title = notification.example.metadata[:title]
full_description = notification.example.metadata[:full_description] 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) set_status_mapping(control_id, status)
collect_notifications(notification, control_id, status) collect_notifications(notification, control_id, status)
control_ended = control_ended?(control_id) control_ended = control_ended?(control_id)

View file

@ -65,15 +65,33 @@ end
control "tmp-6.0.1" do control "tmp-6.0.1" do
impact 0.5 impact 0.5
only_if("Some reason for N/A", impact: 0.0) { false } only_if("Some reason for N/A", impact: 0.0) { false }
describe file("/tmp") do describe "f.1" do
it { should be_directory } it { should cmp "f.1" }
end end
end end
# Example of setting impact using code and not marked as N/A # Example of setting impact using code and not marked as N/A
control "tmp-6.0.2" do control "tmp-6.0.2" do
only_if(impact: 0.5) { false } only_if(impact: 0.5) { false }
describe file("/tmp") do describe "f.2" do
it { should be_directory } 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
end end

View file

@ -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 "[N/R] tmp-3.0.2"
_(out.stderr).must_include "[FAILED] tmp-4.0" _(out.stderr).must_include "[FAILED] tmp-4.0"
_(out.stderr).must_include "[PASSED] tmp-5.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 assert_exit_code 100, out
end end

View file

@ -1347,12 +1347,12 @@ EOT
it "should show enhanced_outcomes for controls with impact 0" do it "should show enhanced_outcomes for controls with impact 0" do
_(run_result.stdout).must_include "5 skipped" _(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" _(run_result.stdout).must_include "N/A"
end end
it "should show enhanced_outcomes for controls with errors" do 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 "2 controls have error"
_(run_result.stdout).must_include "ERR" _(run_result.stdout).must_include "ERR"
end end
@ -1362,7 +1362,7 @@ EOT
end end
it "should show enhanced_outcomes for passed controls" do 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 end
it "should mark control as N/A using zero impact from only_if" do 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" _(run_result.stdout).must_include "N/R tmp-6.0.2"
end end
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 end
describe "when running profile with enhanced_outcomes option and yaml reporter" do describe "when running profile with enhanced_outcomes option and yaml reporter" do