mirror of
https://github.com/inspec/inspec
synced 2024-11-26 22:50:36 +00:00
Merge pull request #5011 from inspec/cw/complex-cli-inputs
Boolean, Numeric, and Structured CLI inputs
This commit is contained in:
commit
fb1aeb0655
6 changed files with 237 additions and 7 deletions
|
@ -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.
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
6
test/fixtures/profiles/inputs/cli/inspec.yml
vendored
6
test/fixtures/profiles/inputs/cli/inspec.yml
vendored
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue