mirror of
https://github.com/inspec/inspec
synced 2024-11-23 13:13:22 +00:00
fc1ad67ffc
Signed-off-by: Ryan Davis <zenspider@chef.io>
360 lines
15 KiB
Ruby
360 lines
15 KiB
Ruby
# Functional tests related to plugin facility
|
|
require "functional/helper"
|
|
|
|
# I wrapped the whole file in a describe to refactor the include and
|
|
# add parallelization. I didn't want to reindent the whole file until
|
|
# we know this works well.
|
|
|
|
# rubocop:disable Layout/IndentationConsistency
|
|
|
|
describe "plugins" do
|
|
include FunctionalHelper
|
|
parallelize_me!
|
|
|
|
#=========================================================================================#
|
|
# Loader Errors
|
|
#=========================================================================================#
|
|
describe "plugin loader" do
|
|
it "handles an unloadable plugin correctly" do
|
|
outcome = inspec_with_env("version", INSPEC_CONFIG_DIR: File.join(config_dir_path, "plugin_error_on_load"))
|
|
|
|
outcome.stdout.must_include("ERROR", "Have an error on stdout")
|
|
outcome.stdout.must_include("Could not load plugin inspec-divide-by-zero", "Name the plugin in the stdout error")
|
|
outcome.stdout.wont_include("ZeroDivisionError", "No stacktrace in error by default")
|
|
outcome.stdout.must_include("Errors were encountered while loading plugins", "Friendly message in error")
|
|
outcome.stdout.must_include("Plugin name: inspec-divide-by-zero", "Plugin named in error")
|
|
outcome.stdout.must_include("divided by 0", "Exception message in error")
|
|
|
|
assert_exit_code 2, outcome
|
|
|
|
# TODO: split
|
|
outcome = inspec_with_env("version --debug", INSPEC_CONFIG_DIR: File.join(config_dir_path, "plugin_error_on_load"))
|
|
|
|
outcome.stdout.must_include("ZeroDivisionError", "Include stacktrace in error with --debug")
|
|
|
|
assert_exit_code 2, outcome
|
|
end
|
|
end
|
|
|
|
#=========================================================================================#
|
|
# Disabling Plugins
|
|
#=========================================================================================#
|
|
describe "when disabling plugins" do
|
|
describe "when disabling the core plugins" do
|
|
it "should not be able to use core-provided commands" do
|
|
run_result = run_inspec_process("--disable-core-plugins habitat")
|
|
run_result.stderr.must_include 'Could not find command "habitat".'
|
|
|
|
# One might think that this should be code 2 (plugin error)
|
|
# But because the core plugins are not loaded, 'habitat' is not
|
|
# a known command, which makes it a usage error, code 1.
|
|
assert_exit_code 1, run_result
|
|
end
|
|
end
|
|
|
|
describe "when disabling the user plugins" do
|
|
it "should not be able to use user commands" do
|
|
run_result = run_inspec_process("--disable-user-plugins meaningoflife answer", env: { INSPEC_CONFIG_DIR: File.join(config_dir_path, "meaning_by_path") })
|
|
|
|
run_result.stderr.must_include 'Could not find command "meaningoflife"'
|
|
|
|
assert_exit_code 1, run_result
|
|
end
|
|
end
|
|
end
|
|
|
|
#=========================================================================================#
|
|
# CliCommand plugin type
|
|
#=========================================================================================#
|
|
describe "cli command plugins" do
|
|
it "is able to respond to a plugin-based cli subcommand" do
|
|
outcome = inspec_with_env("meaningoflife answer", INSPEC_CONFIG_DIR: File.join(config_dir_path, "meaning_by_path"))
|
|
|
|
outcome.stderr.wont_include 'Could not find command "meaningoflife"'
|
|
outcome.stderr.must_equal ""
|
|
|
|
outcome.stdout.must_equal ""
|
|
|
|
assert_exit_code 42, outcome
|
|
end
|
|
|
|
it "is able to respond to [help subcommand] invocations" do
|
|
outcome = inspec_with_env("help meaningoflife", INSPEC_CONFIG_DIR: File.join(config_dir_path, "meaning_by_path"))
|
|
|
|
outcome.stderr.must_equal ""
|
|
|
|
outcome.stdout.must_include "inspec meaningoflife answer"
|
|
# Full text:
|
|
# 'Exits immediately with an exit code reflecting the answer to life the universe, and everything.'
|
|
# but Thor will ellipsify based on the terminal width
|
|
outcome.stdout.must_include "Exits immediately"
|
|
|
|
assert_exit_code 0, outcome
|
|
end
|
|
|
|
# This is an important test; usually CLI plugins are only activated when their name is present in ARGV
|
|
it "includes plugin-based cli commands in top-level help" do
|
|
outcome = inspec_with_env("help", INSPEC_CONFIG_DIR: File.join(config_dir_path, "meaning_by_path"))
|
|
|
|
outcome.stdout.must_include "inspec meaningoflife"
|
|
|
|
assert_exit_code 0, outcome
|
|
end
|
|
end
|
|
|
|
#=========================================================================================#
|
|
# Input plugin type
|
|
#=========================================================================================#
|
|
describe "input plugins" do
|
|
let(:env) { { INSPEC_CONFIG_DIR: "#{config_dir_path}/input_plugin" } }
|
|
let(:profile) { "#{profile_path}/inputs/plugin" }
|
|
def run_input_plugin_test_with_controls(controls)
|
|
cmd = "exec #{profile} --controls #{controls}"
|
|
run_result = run_inspec_process(cmd, json: true, env: env)
|
|
run_result.must_have_all_controls_passing
|
|
run_result.stderr.must_be_empty
|
|
end
|
|
|
|
describe "when an input is provided only by a plugin" do
|
|
it "should find the value" do
|
|
run_input_plugin_test_with_controls("only_in_plugin")
|
|
end
|
|
end
|
|
|
|
describe "when an input is provided both inline and by a higher-precedence plugin" do
|
|
it "should use the value from the plugin" do
|
|
run_input_plugin_test_with_controls("collide_plugin_higher")
|
|
end
|
|
end
|
|
|
|
describe "when an input is provided both inline and by a lower-precedence plugin" do
|
|
it "should use the value from inline" do
|
|
run_input_plugin_test_with_controls("collide_inline_higher")
|
|
end
|
|
end
|
|
|
|
describe "when examining the event log" do
|
|
it "should include the expected events" do
|
|
run_input_plugin_test_with_controls("event_log")
|
|
end
|
|
end
|
|
|
|
describe "when listing available inputs" do
|
|
it "should list available inputs" do
|
|
run_input_plugin_test_with_controls("list_events")
|
|
end
|
|
end
|
|
end
|
|
|
|
#=========================================================================================#
|
|
# inspec plugin command
|
|
#=========================================================================================#
|
|
# See lib/plugins/inspec-plugin-manager-cli/test
|
|
|
|
#=========================================================================================#
|
|
# Plugin Disable Messaging
|
|
#=========================================================================================#
|
|
describe "disable plugin usage message integration" do
|
|
it "mentions the --disable-{user,core}-plugins options" do
|
|
outcome = inspec("help")
|
|
["--disable-user-plugins", "--disable-core-plugins"].each do |option|
|
|
outcome.stdout.must_include(option)
|
|
end
|
|
end
|
|
end
|
|
|
|
#=========================================================================================#
|
|
# DSL Plugin Support
|
|
#=========================================================================================#
|
|
|
|
describe "DSL plugin types support" do
|
|
include PluginFunctionalHelper
|
|
|
|
let(:fixture_path) { File.join(profile_path, "dsl_plugins", "controls", profile_file) }
|
|
let(:dsl_plugin_path) { File.join(mock_path, "plugins", "inspec-dsl-test", "lib", "inspec-dsl-test.rb") }
|
|
let(:run_result) { run_inspec_with_plugin("exec #{fixture_path}", plugin_path: dsl_plugin_path) }
|
|
let(:json_result) { run_result.payload.json }
|
|
|
|
describe "outer profile dsl plugin type support" do
|
|
let(:profile_file) { "outer_profile_dsl.rb" }
|
|
it "works correctly with outer_profile dsl extensions" do
|
|
run_result.stderr.must_equal ""
|
|
|
|
# The outer_profile_dsl.rb file has control-01, then a call to favorite_grain
|
|
# (which generates a control), then control-03.
|
|
# If the plugin exploded, we'd see control-01 but not control-03
|
|
controls = json_result["profiles"][0]["controls"]
|
|
controls.count.must_equal 3
|
|
|
|
# We expect the second controls id to be 'sorghum'
|
|
# (this is the functionality of the outer_profile_dsl we installed)
|
|
generated_control = json_result["profiles"][0]["controls"][1]
|
|
generated_control["id"].must_equal "sorghum"
|
|
generated_control["results"][0]["status"].must_equal "passed"
|
|
end
|
|
end
|
|
|
|
describe "control dsl plugin type support" do
|
|
|
|
let(:profile_file) { "control_dsl.rb" }
|
|
it "works correctly with control dsl extensions" do
|
|
run_result.stderr.must_equal ""
|
|
|
|
# The control_dsl.rb file has one control, with a describe-01, then a call to favorite_fruit, then describe-02
|
|
# If the plugin exploded, we'd see describe-01 but not describe-02
|
|
results = json_result["profiles"][0]["controls"][0]["results"]
|
|
results.count.must_equal 2
|
|
|
|
# We expect the descriptions to include that the favorite fruit is banana
|
|
# (this is the functionality of the control_dsl we installed)
|
|
first_description_section = json_result["profiles"][0]["controls"][0]["descriptions"].first
|
|
first_description_section.wont_be_nil
|
|
first_description_section["label"].must_equal "favorite_fruit"
|
|
first_description_section["data"].must_equal "Banana"
|
|
end
|
|
end
|
|
|
|
describe "describe dsl plugin type support" do
|
|
let(:profile_file) { "describe_dsl.rb" }
|
|
it "works correctly with describe dsl extensions" do
|
|
run_result.stderr.must_equal ""
|
|
|
|
# The describe_dsl.rb file has one control, with
|
|
# describe-01, describe-02 which contains a call to favorite_vegetable, then describe-03
|
|
# If the plugin exploded, we'd see describe-01 but not describe-02
|
|
results = json_result["profiles"][0]["controls"][0]["results"]
|
|
results.count.must_equal 3
|
|
|
|
# We expect the description of describe-02 to include the word aubergine
|
|
# (this is the functionality of the describe_dsl we installed)
|
|
second_result = json_result["profiles"][0]["controls"][0]["results"][1]
|
|
second_result.wont_be_nil
|
|
second_result["code_desc"].must_include "aubergine"
|
|
end
|
|
end
|
|
|
|
describe "test dsl plugin type support" do
|
|
let(:profile_file) { "test_dsl.rb" }
|
|
it "works correctly with test dsl extensions" do
|
|
run_result.stderr.must_equal ""
|
|
|
|
# The test_dsl.rb file has one control, with
|
|
# describe-01, describe-02 which contains a call to favorite_legume, then describe-03
|
|
# If the plugin exploded, we'd see describe-01 but not describe-02
|
|
results = json_result["profiles"][0]["controls"][0]["results"]
|
|
results.count.must_equal 3
|
|
|
|
# I spent a while trying to find a way to get the test to alter its name;
|
|
# that won't work for various setup reasons.
|
|
# So, it just throws an exception with the word 'edemame' in it.
|
|
second_result = json_result["profiles"][0]["controls"][0]["results"][1]
|
|
second_result.wont_be_nil
|
|
second_result["status"].must_equal "failed"
|
|
second_result["message"].must_include "edemame"
|
|
end
|
|
end
|
|
|
|
describe "resource dsl plugin type support" do
|
|
let(:profile_file) { "unused" }
|
|
it "works correctly with test dsl extensions" do
|
|
# We have to build a custom command line - need to load the whole profile,
|
|
# so the libraries get loaded.
|
|
cmd = "exec "
|
|
cmd += File.join(profile_path, "dsl_plugins")
|
|
cmd += " --controls=/^rdsl-control/ "
|
|
run_result = run_inspec_with_plugin(cmd, plugin_path: dsl_plugin_path)
|
|
run_result.stderr.must_equal ""
|
|
|
|
# We should have three controls; 01 and 03 just do a string match.
|
|
# 02 uses the custom resource, which relies on calls to the resource DSL.
|
|
# If the plugin exploded, we'd see rdsl-control-01 but not rdsl-control-02
|
|
json_result = run_result.payload.json
|
|
results = json_result["profiles"][0]["controls"]
|
|
results.count.must_equal 3
|
|
|
|
# Control 2 has 2 describes; one uses a simple explicit matcher,
|
|
# while the second uses a matcher defined via a macro provided by the resource DSL.
|
|
control2_results = results[1]["results"]
|
|
control2_results[0]["status"].must_equal "passed"
|
|
control2_results[0]["code_desc"].must_include "favorite_berry"
|
|
control2_results[0]["code_desc"].must_include "blendable"
|
|
|
|
control2_results[1]["status"].must_equal "passed"
|
|
control2_results[1]["code_desc"].must_include "favorite_berry"
|
|
control2_results[1]["code_desc"].must_include "have drupals"
|
|
end
|
|
end
|
|
end
|
|
|
|
#=========================================================================================#
|
|
# Train Plugin Support
|
|
#=========================================================================================#
|
|
|
|
describe "train plugin support" do
|
|
describe "when a train plugin is installed" do
|
|
it "can run inspec detect against a URL target" do
|
|
outcome = inspec_with_env("detect -t test-fixture://", INSPEC_CONFIG_DIR: File.join(config_dir_path, "train-test-fixture"))
|
|
|
|
lines = outcome.stdout.split("\n")
|
|
lines.grep(/Name/).first.must_include("test-fixture")
|
|
lines.grep(/Name/).first.wont_include("train-test-fixture")
|
|
lines.grep(/Release/).first.must_include("0.1.0")
|
|
lines.grep(/Families/).first.must_include("os")
|
|
lines.grep(/Families/).first.must_include("windows")
|
|
lines.grep(/Families/).first.must_include("unix")
|
|
lines.grep(/Arch/).first.must_include("mock")
|
|
|
|
outcome.stderr.must_be_empty
|
|
|
|
assert_exit_code 0, outcome
|
|
end
|
|
|
|
it "can run inspec detect against a test-fixture backend" do
|
|
outcome = inspec_with_env("detect -b test-fixture", INSPEC_CONFIG_DIR: File.join(config_dir_path, "train-test-fixture"))
|
|
lines = outcome.stdout.split("\n")
|
|
lines.grep(/Name/).first.must_include("test-fixture")
|
|
lines.grep(/Name/).first.wont_include("train-test-fixture")
|
|
lines.grep(/Release/).first.must_include("0.1.0")
|
|
lines.grep(/Families/).first.must_include("os")
|
|
lines.grep(/Families/).first.must_include("windows")
|
|
lines.grep(/Families/).first.must_include("unix")
|
|
lines.grep(/Arch/).first.must_include("mock")
|
|
|
|
outcome.stderr.must_be_empty
|
|
|
|
assert_exit_code 0, outcome
|
|
end
|
|
|
|
it "can run inspec shell and read a file" do
|
|
outcome = inspec_with_env("shell -t test-fixture:// -c 'file(\"any-path\").content'", INSPEC_CONFIG_DIR: File.join(config_dir_path, "train-test-fixture"))
|
|
skip_windows!
|
|
outcome.stdout.chomp.must_equal "Lorem Ipsum"
|
|
|
|
outcome.stderr.must_be_empty
|
|
|
|
assert_exit_code 0, outcome
|
|
end
|
|
|
|
it "can run inspec shell and run a command" do
|
|
outcome = inspec_with_env("shell -t test-fixture:// -c 'command(\"echo hello\").exit_status'", INSPEC_CONFIG_DIR: File.join(config_dir_path, "train-test-fixture"))
|
|
|
|
skip_windows!
|
|
outcome.stdout.chomp.must_equal "17"
|
|
|
|
outcome.stderr.must_be_empty
|
|
|
|
assert_exit_code 0, outcome
|
|
|
|
# TODO: split
|
|
outcome = inspec_with_env("shell -t test-fixture:// -c 'command(\"echo hello\").stdout'", INSPEC_CONFIG_DIR: File.join(config_dir_path, "train-test-fixture"))
|
|
|
|
outcome.stdout.chomp.must_equal "Mock Command Result stdout"
|
|
|
|
outcome.stderr.must_be_empty
|
|
|
|
assert_exit_code 0, outcome
|
|
end
|
|
end
|
|
end
|
|
|
|
end
|