Merge pull request #5011 from inspec/cw/complex-cli-inputs

Boolean, Numeric, and Structured CLI inputs
This commit is contained in:
Nick Schwaderer 2020-06-05 04:23:48 -07:00 committed by GitHub
commit fb1aeb0655
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 237 additions and 7 deletions

View file

@ -263,6 +263,19 @@ To set multiple inputs, say:
inspec exec my_profile --input input_name1=input_value1 name2=value2
```
If a CLI input value resembles a number, it will be converted to an Integer or Float. Scientific notation is not currently recognized.
```yaml
inspec exec my_profile --input amplifier_volume=-11
inspec exec my_profile --input water_depth=11.5
```
You may set inputs with complex values, such as arrays and hashes using either YAML or JSON syntax. Just be sure to protect the string from the shell using single quotes.
```yaml
inspec exec my_profile --input alphabet='[a,b,c]'
inspec exec my_profile --input fruits='{a: apples, b: bananas, c: cantelopes}'
inspec exec my_profile --input json_fruit='{"a": "apples", "g": ["grape01", "grape02"] }'
```
Do not repeat the `--input` flag; that will override the previous setting.
CLI-set inputs have a priority of 50.
@ -336,4 +349,4 @@ pp input_object('troublesome_input').events
`diagnostic_string` assembles the Event Log into a printable log message for convenience.
The Event Log contains entries for every time that the value changed, as well as one for when the input was first created. When possible, stack probing is used to determine file and line numbers. Most importantly, you will see priority numbers; remember that highest priority wins; order only matters to break a tie.
The Event Log contains entries for every time that the value changed, as well as one for when the input was first created. When possible, stack probing is used to determine file and line numbers. Most importantly, you will see priority numbers; remember that highest priority wins; order only matters to break a tie.

View file

@ -140,7 +140,7 @@ module Inspec
option :reporter_backtrace_inclusion, type: :boolean,
desc: "Include a code backtrace in report data (default: true)"
option :input, type: :array, banner: "name1=value1 name2=value2",
desc: "Specify one or more inputs directly on the command line, as --input NAME=VALUE"
desc: "Specify one or more inputs directly on the command line, as --input NAME=VALUE. Accepts single-quoted YAML and JSON structures."
option :input_file, type: :array,
desc: "Load one or more input files, a YAML file with values for the profile to use"
option :waiver_file, type: :array,

View file

@ -166,8 +166,9 @@ module Inspec
end
end
input_name, input_value = pair.split("=")
input_value = parse_cli_input_value(input_name, input_value)
evt = Inspec::Input::Event.new(
value: input_value.chomp(","), # Trim trailing comma if any
value: input_value,
provider: :cli,
priority: 50
)
@ -175,6 +176,37 @@ module Inspec
end
end
# Remove trailing commas, resolve type.
def parse_cli_input_value(input_name, given_value)
value = given_value.chomp(",") # Trim trailing comma if any
case value
when /^true|false$/i
value = !!(value =~ /true/i)
when /^-?\d+$/
value = value.to_i
when /^-?\d+\.\d+$/
value = value.to_f
when /^(\[|\{).*(\]|\})$/
# Look for complex values and try to parse them.
require "yaml"
begin
value = YAML.load(value)
rescue Psych::SyntaxError => yaml_error
# It could be that we just tried to run JSON through the YAML parser.
require "json"
begin
value = JSON.parse(value)
rescue JSON::ParserError => json_error
msg = "Unparseable value '#{value}' for --input #{input_name}.\n"
msg += "When treated as YAML, error: #{yaml_error.message}\n"
msg += "When treated as JSON, error: #{json_error.message}"
Inspec::Log.warn msg
end
end
end
value
end
def bind_inputs_from_runner_api(profile_name, input_hash)
# TODO: move this into a core plugin

View file

@ -8,3 +8,83 @@ control "test_control_02" do
it { should cmp "value_from_cli_02"}
end
end
control "test_control_numeric_implicit" do
describe input("test_input_03", value: "value_from_dsl") do
it { should be_a Integer }
end
end
control "test_control_numeric_type" do
describe input("test_input_04", value: "value_from_dsl") do
it { should be_a Integer }
end
end
control "test_control_numeric_float" do
describe input("test_input_05", value: "value_from_dsl") do
it { should be_a Float }
end
end
control "test_control_yaml_array" do
describe input("test_input_06", value: "value_from_dsl") do
it { should eq [ "a", "b", "c" ] }
end
end
control "test_control_yaml_hash" do
describe input("test_input_07", value: "value_from_dsl") do
# Parser keeps tripping up on this when inlined
expected = { "a" => "apples", "b" => "bananas", "c" => "cantelopes" }
it { should eq expected }
end
end
control "test_control_yaml_deep" do
describe input("test_input_09", value: "value_from_dsl") do
# Parser keeps tripping up on this when inlined
expected = { "a" => "apples", "g" => ["grape01", "grape02"] }
it { should eq expected }
end
end
control "test_control_json_array" do
describe input("test_input_10", value: "value_from_dsl") do
it { should eq [ "a", "b", "c" ] }
end
end
control "test_control_json_hash" do
describe input("test_input_11", value: "value_from_dsl") do
# Parser keeps tripping up on this when inlined
expected = { "a" => "apples", "b" => "bananas", "c" => "cantelopes" }
it { should eq expected }
end
end
control "test_control_json_deep" do
describe input("test_input_12", value: "value_from_dsl") do
# Parser keeps tripping up on this when inlined
expected = { "a" => "apples", "g" => ["grape01", "grape02"] }
it { should eq expected }
end
end
control "test_control_bool_true" do
describe input("test_input_13", value: "value_from_dsl") do
it { should be_a TrueClass }
end
end
control "test_control_bool_false" do
describe input("test_input_14", value: "value_from_dsl") do
it { should be_a FalseClass }
end
end
control "test_control_bool_true_caps" do
describe input("test_input_15", value: "value_from_dsl") do
it { should be_a TrueClass }
end
end

View file

@ -3,4 +3,8 @@ license: Apache-2.0
summary: Profile to exercise setting inputs using the --input CLI option
version: 0.1.0
supports:
platform: os
platform: os
inputs:
- name: test_input_04
type: numeric

View file

@ -158,19 +158,120 @@ describe "inputs" do
describe "when the --input is used once with two values" do
let(:input_opt) { "--input test_input_01=value_from_cli_01 test_input_02=value_from_cli_02" }
it("correctly reads the input") { assert_json_controls_passing(result) }
let(:control_opt) { "--controls test_control_01 test_control_02" }
it("correctly reads both inputs") { assert_json_controls_passing(result) }
end
describe "when the --input is used once with two values and a comma" do
let(:input_opt) { "--input test_input_01=value_from_cli_01, test_input_02=value_from_cli_02" }
it("correctly reads the input") { assert_json_controls_passing(result) }
let(:control_opt) { "--controls test_control_01 test_control_02" }
it("correctly reads both inputs ignoring the comma") { assert_json_controls_passing(result) }
end
# See https://github.com/inspec/inspec/issues/4977
describe "when the --input is used with a numeric value" do
describe "when the --input is used with an integer value" do
let(:input_opt) { "--input test_input_03=11" }
let(:control_opt) { "--controls test_control_numeric_implicit" }
it("correctly reads the input as numeric") { assert_json_controls_passing(result) }
end
describe "when the --input is used with a integer value and is declared as a numeric type in metadata" do
let(:input_opt) { "--input test_input_04=11" }
let(:control_opt) { "--controls test_control_numeric_type" }
it("correctly reads the input as numeric") { assert_json_controls_passing(result) }
end
describe "when the --input is used with a float value" do
let(:input_opt) { "--input test_input_05=-11.0" }
let(:control_opt) { "--controls test_control_numeric_float" }
it("correctly reads the input as numeric") { assert_json_controls_passing(result) }
end
end
# See https://github.com/inspec/inspec/issues/4799
describe "when the --input is used with a boolean value" do
describe "when the --input is passed true" do
let(:input_opt) { "--input test_input_13=true" }
let(:control_opt) { "--controls test_control_bool_true" }
it("correctly reads the input as TrueClass") { assert_json_controls_passing(result) }
end
describe "when the --input is passed false" do
let(:input_opt) { "--input test_input_14=false" }
let(:control_opt) { "--controls test_control_bool_false" }
it("correctly reads the input as FalseClass") { assert_json_controls_passing(result) }
end
describe "when the --input is passed TRUE" do
let(:input_opt) { "--input test_input_15=TRUE" }
let(:control_opt) { "--controls test_control_bool_true_caps" }
it("correctly reads the input as a TrueClass even when capitalized") { assert_json_controls_passing(result) }
end
end
describe "when the --input is a complex structure" do
# Garbage
describe "when the --input is malformed YAML " do
let(:input_opt) { "--input test_input_08='[a, b, }]'" }
it "runs with failed tests and provides a YAML warning message" do
output = result.stderr
assert_includes output, "WARN"
assert_includes output, "treated as YAML"
assert_includes output, "test_input_08"
assert_exit_code(100, result)
end
end
# YAML
describe "when the --input is a YAML array" do
let(:input_opt) { "--input test_input_06='[a,b,c]'" }
let(:control_opt) { "--controls test_control_yaml_array" }
it("correctly reads the input as a yaml array") { assert_json_controls_passing(result) }
end
describe "when the --input is a YAML hash" do
# Note: this produces a String-keyed Hash
let(:input_opt) { "--input test_input_07='{a: apples, b: bananas, c: cantelopes}'" }
let(:control_opt) { "--controls test_control_yaml_hash" }
it("correctly reads the input as a yaml hash") { assert_json_controls_passing(result) }
end
describe "when the --input is a YAML deep structure" do
# Note: this produces a String-keyed Hash
let(:input_opt) { "--input test_input_09='{a: apples, g: [grape01, grape02] }'" }
let(:control_opt) { "--controls test_control_yaml_deep" }
it("correctly reads the input as a yaml struct") { assert_json_controls_passing(result) }
end
# JSON mode
# These three were tested manually on 2020-05-05 on win2016 and passed
# Under CI however, we have multiple layers of quoting and dequoting
# and it breaks badly. https://github.com/inspec/inspec/issues/5015
unless windows?
describe "when the --input is a JSON array" do
let(:input_opt) { %q{--input test_input_10='["a","b","c"]'} }
let(:control_opt) { "--controls test_control_json_array" }
it("correctly reads the input as a json array") {
assert_empty result.stderr
assert_json_controls_passing(result)
}
end
describe "when the --input is a JSON hash" do
# Note: this produces a String-keyed Hash
let(:input_opt) { %q{--input test_input_11='{"a": "apples", "b": "bananas", "c": "cantelopes"}'} }
let(:control_opt) { "--controls test_control_json_hash" }
it("correctly reads the input as a json hash") { assert_json_controls_passing(result) }
end
describe "when the --input is a JSON deep structure" do
# Note: this produces a String-keyed Hash
let(:input_opt) { %q{--input test_input_12='{"a": "apples", "g": ["grape01", "grape02"] }'} }
let(:control_opt) { "--controls test_control_json_deep" }
it("correctly reads the input as a json struct") { assert_json_controls_passing(result) }
end
end
end
describe "when the --input is used twice with one value each" do
let(:input_opt) { "--input test_input_01=value_from_cli_01 --input test_input_02=value_from_cli_02" }
let(:control_opt) { "--controls test_control_02" }
# Expected, though unfortunate, behavior is to only notice the second input
it("correctly reads the input") { assert_json_controls_passing(result) }
it("correctly reads the second input") { assert_json_controls_passing(result) }
end
describe "when the --input is used with no equal sign" do