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
|
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`
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Reference in a new issue