Merge pull request #5466 from inspec/nm/inputs-regex

New input option `pattern` added for DSL and metadata inputs
This commit is contained in:
Clinton Wolfe 2021-04-21 19:34:07 -04:00 committed by GitHub
commit 79aa03ae14
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 164 additions and 6 deletions

View file

@ -416,6 +416,12 @@ input values that are used as test results.
Allowed in: Metadata
### Pattern
Optional, `Regexp`. This feature validates the input by matching it with the provided regular expression.
Allowed in: DSL, Metadata
## Advanced Topics
### Debugging Inputs with the Event Log

View file

@ -19,12 +19,17 @@ module Inspec
attr_accessor :input_name
attr_accessor :input_value
attr_accessor :input_type
attr_accessor :input_pattern
end
class TypeError < Error
attr_accessor :input_type
end
class PatternError < Error
attr_accessor :input_pattern
end
class RequiredError < Error
attr_accessor :input_name
end
@ -56,7 +61,6 @@ module Inspec
def initialize(properties = {})
@value_has_been_set = false
properties.each do |prop_name, prop_value|
if EVENT_PROPERTIES.include? prop_name
# OK, save the property
@ -174,7 +178,7 @@ module Inspec
# are free to go higher.
DEFAULT_PRIORITY_FOR_VALUE_SET = 60
attr_reader :description, :events, :identifier, :name, :required, :sensitive, :title, :type
attr_reader :description, :events, :identifier, :name, :required, :sensitive, :title, :type, :pattern
def initialize(name, options = {})
@name = name
@ -192,7 +196,6 @@ module Inspec
# debugging record of when and how the value changed.
@events = []
events.push make_creation_event(options)
update(options)
end
@ -213,6 +216,7 @@ module Inspec
def update(options)
_update_set_metadata(options)
normalize_type_restriction!
normalize_pattern_restriction!
# Values are set by passing events in; but we can also infer an event.
if options.key?(:value) || options.key?(:default)
@ -227,6 +231,7 @@ module Inspec
events << options[:event] if options.key? :event
enforce_type_restriction!
enforce_pattern_restriction!
end
# We can determine a value:
@ -268,6 +273,7 @@ module Inspec
@identifier = options[:identifier] if options.key?(:identifier) # TODO: determine if this is ever used
@type = options[:type] if options.key?(:type)
@sensitive = options[:sensitive] if options.key?(:sensitive)
@pattern = options[:pattern] if options.key?(:pattern)
end
def make_creation_event(options)
@ -310,7 +316,9 @@ module Inspec
file: location.path,
line: location.lineno
)
enforce_type_restriction!
enforce_pattern_restriction!
end
def value
@ -324,7 +332,7 @@ module Inspec
def to_hash
as_hash = { name: name, options: {} }
%i{description title identifier type required value sensitive}.each do |field|
%i{description title identifier type required value sensitive pattern}.each do |field|
val = send(field)
next if val.nil?
@ -407,6 +415,33 @@ module Inspec
@type = type_req
end
def enforce_pattern_restriction!
return unless pattern
return unless has_value?
string_value = current_value(false).to_s
valid_pattern = string_value.match?(pattern)
unless valid_pattern
error = Inspec::Input::ValidationError.new
error.input_name = @name
error.input_value = string_value
error.input_pattern = pattern
raise error, "Input '#{error.input_name}' with value '#{error.input_value}' does not validate to pattern '#{error.input_pattern}'."
end
end
def normalize_pattern_restriction!
return unless pattern
unless valid_regexp?(pattern)
error = Inspec::Input::PatternError.new
error.input_pattern = pattern
raise error, "Pattern '#{error.input_pattern}' is not a valid regex pattern."
end
@pattern = pattern
end
def valid_numeric?(value)
Float(value)
true

View file

@ -325,6 +325,7 @@ module Inspec
type: input_options[:type],
required: input_options[:required],
sensitive: input_options[:sensitive],
pattern: input_options[:pattern],
event: evt
)
end

View file

@ -20,7 +20,7 @@ module Inspec
def to_hash
as_hash = { name: name, options: {} }
%i{description title identifier type required value}.each do |field|
%i{description title identifier type required value pattern}.each do |field|
val = send(field)
next if val.nil?

View file

@ -180,7 +180,15 @@ module Inspec
options[:priority] ||= 20
options[:provider] = :inline_control_code
evt = Inspec::Input.infer_event(options)
Inspec::InputRegistry.find_or_register_input(input_name, __profile_id, event: evt).value
Inspec::InputRegistry.find_or_register_input(
input_name,
__profile_id,
type: options[:type],
required: options[:required],
description: options[:description],
pattern: options[:pattern],
event: evt
).value
end
end

View file

@ -0,0 +1,38 @@
# copyright: 2021, Chef Software, Inc.
title "Testing all option flags on input through DSL"
control "pattern_flag_success_check" do
describe input("input_value_01", value: 5, pattern: "^\d*[13579]$") do
it { should eq 5 }
end
end
control "pattern_flag_failure_check" do
describe input("input_value_02", value: 2, pattern: "^\d*[13579]$") do
it { should eq 2 }
end
end
control "required_flag_success_check" do
describe input("input_value_03", value: 5, required: true) do
it { should eq 5 }
end
end
control "required_flag_failure_check" do
describe input("input_value_04", required: true) do
it { should eq 5 }
end
end
control "type_flag_success_check" do
describe input("input_value_05", value: 5, type: "Numeric") do
it { should eq 5 }
end
end
control "type_flag_failure_check" do
describe input("input_value_06", value: 5, type: "String") do
it { should eq 5 }
end
end

View file

@ -0,0 +1,9 @@
name: dsl
title: InSpec Profile to test all option flags on input through dsl
maintainer: Chef Software, Inc.
copyright: Chef Software, Inc.
license: Apache-2.0
summary: A profile that tests all option flags on input through dsl
version: 0.1.0
supports:
platform: os

View file

@ -0,0 +1,8 @@
# copyright: 2021, Chef Software, Inc.
title "Testing :pattern flag"
control "pattern_flag_checking_odd_num" do
describe input("input_value_01") do
it { should eq 5 }
end
end

View file

@ -0,0 +1,14 @@
name: metadata-pattern
title: InSpec Profile to test :pattern flag on inputs using metadata
maintainer: Chef Software, Inc.
copyright: Chef Software, Inc.
license: Apache-2.0
summary: A profile that tests the :pattern flag on inputs
version: 0.1.0
supports:
platform: os
inputs:
- name: input_value_01
value: 5
pattern: ^\d*[13579]$
required: true

View file

@ -462,4 +462,40 @@ describe "inputs" do
assert_json_controls_passing(result)
end
end
describe "when a profile is used with input options" do
it "should be a success for valid values when pattern flag is passed through metadata file" do
result = run_inspec_process("exec #{inputs_profiles_path}/metadata-pattern", json: true)
_(result.stderr).must_be_empty
assert_json_controls_passing(result)
end
it "should be a success for valid values when required, type and pattern flag is passed through dsl" do
result = run_inspec_process("exec #{inputs_profiles_path}/dsl --controls pattern_flag_success_check required_flag_success_check type_flag_success_check", json: true)
_(result.stderr).must_be_empty
assert_json_controls_passing(result)
end
it "should be a failure for invalid value when required flag is passed through dsl" do
result = run_inspec_process("exec #{inputs_profiles_path}/dsl --controls required_flag_failure_check", json: true)
_(result.stderr).must_include "Input 'input_value_04' is required and does not have a value.\n"
assert_exit_code 1, result
end
it "should be a failure for invalid value when type flag is passed through dsl" do
result = run_inspec_process("exec #{inputs_profiles_path}/dsl --controls type_flag_failure_check", json: true)
_(result.stderr).must_be_empty
output = JSON.parse(result[0])
assert_equal "failed", output["profiles"][0]["controls"][0]["results"][0]["status"]
assert_exit_code(100, result)
end
it "should be a failure for invalid value when pattern flag is passed through dsl" do
result = run_inspec_process("exec #{inputs_profiles_path}/dsl --controls pattern_flag_failure_check", json: true)
_(result.stderr).must_be_empty
output = JSON.parse(result[0])
assert_equal "failed", output["profiles"][0]["controls"][0]["results"][0]["status"]
assert_exit_code(100, result)
end
end
end

View file

@ -15,6 +15,7 @@ describe Inspec::Input do
required: true,
title: "how is this different than description",
type: "Numeric",
pattern: "^[0-9][0-9]$",
}.each do |field, value|
it "should be able to recall the #{field} field" do
opts[field] = value
@ -32,6 +33,7 @@ describe Inspec::Input do
title: "Best input ever",
description: "important",
type: "Numeric",
pattern: "^[0-9][0-9]$",
required: true)
_(input.to_hash).must_equal({
@ -41,6 +43,7 @@ describe Inspec::Input do
title: "Best input ever",
description: "important",
type: "Numeric",
pattern: "^[0-9][0-9]$",
required: true,
},
})