From 5a7a48bb62a397b1bf570b3cfbc153b5fed56e07 Mon Sep 17 00:00:00 2001 From: Clinton Wolfe Date: Fri, 1 May 2020 23:48:05 -0400 Subject: [PATCH] Basics of a working reporter plugin system Signed-off-by: Clinton Wolfe --- lib/inspec/config.rb | 26 ++++++++++--- lib/inspec/plugin/v2/plugin_types/reporter.rb | 27 +++++++++++++ lib/inspec/reporters.rb | 5 ++- .../config_dirs/reporter_plugin/plugins.json | 10 +++++ .../inspec-reporter-test-fixture/README.md | 3 ++ .../lib/inspec-reporter-test-fixture.rb | 4 ++ .../inspec-reporter-test-fixture/plugin.rb | 13 +++++++ .../inspec-reporter-test-fixture/reporter.rb | 25 ++++++++++++ .../inspec-reporter-test-fixture/version.rb | 5 +++ test/functional/plugins_test.rb | 22 +++++++++++ test/unit/plugin/v2/api_reporter_test.rb | 39 +++++++++++++++++++ 11 files changed, 172 insertions(+), 7 deletions(-) create mode 100644 lib/inspec/plugin/v2/plugin_types/reporter.rb create mode 100644 test/fixtures/config_dirs/reporter_plugin/plugins.json create mode 100644 test/fixtures/plugins/inspec-reporter-test-fixture/README.md create mode 100644 test/fixtures/plugins/inspec-reporter-test-fixture/lib/inspec-reporter-test-fixture.rb create mode 100644 test/fixtures/plugins/inspec-reporter-test-fixture/lib/inspec-reporter-test-fixture/plugin.rb create mode 100644 test/fixtures/plugins/inspec-reporter-test-fixture/lib/inspec-reporter-test-fixture/reporter.rb create mode 100644 test/fixtures/plugins/inspec-reporter-test-fixture/lib/inspec-reporter-test-fixture/version.rb create mode 100644 test/unit/plugin/v2/api_reporter_test.rb diff --git a/lib/inspec/config.rb b/lib/inspec/config.rb index 33505f880..bfd1028b7 100644 --- a/lib/inspec/config.rb +++ b/lib/inspec/config.rb @@ -328,21 +328,35 @@ module Inspec def validate_reporters!(reporters) return if reporters.nil? - # TODO: move this into a reporter plugin type system - valid_types = %w{ - automate - cli + # These "reporters" are actually RSpec Formatters. + # json-rspec is our alias for RSpec's json formatter. + rspec_built_in_formatters = %w{ documentation html + json-rspec + progress + } + + # These are true reporters, but have not been migrated to be plugins yet. + unmigrated_reporters = %w{ + automate + cli json json-automate json-min - json-rspec junit - progress yaml } + # Additional reporters may be loaded via plugins. They will have already been detected at + # this point (see v2_loader.load_all in cli.rb) but they may not (and need not) be + # activated at this point. We only care about their existance their name, for validations sake. + plugin_reporters = Inspec::Plugin::V2::Registry.instance\ + .find_activators(plugin_type: :reporter)\ + .map(&:activator_name).map(&:to_s) + + valid_types = rspec_built_in_formatters + unmigrated_reporters + plugin_reporters + reporters.each do |reporter_name, reporter_config| raise NotImplementedError, "'#{reporter_name}' is not a valid reporter type." unless valid_types.include?(reporter_name) diff --git a/lib/inspec/plugin/v2/plugin_types/reporter.rb b/lib/inspec/plugin/v2/plugin_types/reporter.rb new file mode 100644 index 000000000..d8ab0b9f7 --- /dev/null +++ b/lib/inspec/plugin/v2/plugin_types/reporter.rb @@ -0,0 +1,27 @@ +module Inspec::Plugin::V2::PluginType + class Reporter < Inspec::Plugin::V2::PluginBase + register_plugin_type(:reporter) + + attr_reader :run_data + + def initialize(config) + @config = config + @run_data = config[:run_data] + @output = "" + end + + def output(str, newline = true) + @output << str + @output << "\n" if newline + end + + def rendered_output + @output + end + + # each reporter must implement #render + def render + raise NotImplementedError, "#{self.class} must implement a `#render` method to format its output." + end + end +end diff --git a/lib/inspec/reporters.rb b/lib/inspec/reporters.rb index a334fa690..ae620a9f1 100644 --- a/lib/inspec/reporters.rb +++ b/lib/inspec/reporters.rb @@ -30,7 +30,10 @@ module Inspec::Reporters when "yaml" reporter = Inspec::Reporters::Yaml.new(config) else - raise NotImplementedError, "'#{name}' is not a valid reporter type." + # If we made it here, it must be a plugin, and we know it exists (because we validated it in config.rb) + activator = Inspec::Plugin::V2::Registry.instance.find_activator(plugin_type: :reporter, activator_name: name.to_sym) + activator.activate! + reporter = activator.implementation_class.new(config) end # optional send_report method on reporter diff --git a/test/fixtures/config_dirs/reporter_plugin/plugins.json b/test/fixtures/config_dirs/reporter_plugin/plugins.json new file mode 100644 index 000000000..68a96b860 --- /dev/null +++ b/test/fixtures/config_dirs/reporter_plugin/plugins.json @@ -0,0 +1,10 @@ +{ + "plugins_config_version" : "1.0.0", + "plugins": [ + { + "name": "inspec-reporter-test-fixture", + "installation_type": "path", + "installation_path": "test/fixtures/plugins/inspec-reporter-test-fixture/lib/inspec-reporter-test-fixture.rb" + } + ] +} diff --git a/test/fixtures/plugins/inspec-reporter-test-fixture/README.md b/test/fixtures/plugins/inspec-reporter-test-fixture/README.md new file mode 100644 index 000000000..00a30f25d --- /dev/null +++ b/test/fixtures/plugins/inspec-reporter-test-fixture/README.md @@ -0,0 +1,3 @@ +# inspec-reporter-test-fixture + +Reporter plugin used to test reporter plugin type in test/functional/plugins_test.rb diff --git a/test/fixtures/plugins/inspec-reporter-test-fixture/lib/inspec-reporter-test-fixture.rb b/test/fixtures/plugins/inspec-reporter-test-fixture/lib/inspec-reporter-test-fixture.rb new file mode 100644 index 000000000..ae2821994 --- /dev/null +++ b/test/fixtures/plugins/inspec-reporter-test-fixture/lib/inspec-reporter-test-fixture.rb @@ -0,0 +1,4 @@ +libdir = File.dirname(__FILE__) +$LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir) + +require "inspec-reporter-test-fixture/plugin" diff --git a/test/fixtures/plugins/inspec-reporter-test-fixture/lib/inspec-reporter-test-fixture/plugin.rb b/test/fixtures/plugins/inspec-reporter-test-fixture/lib/inspec-reporter-test-fixture/plugin.rb new file mode 100644 index 000000000..42577aa2a --- /dev/null +++ b/test/fixtures/plugins/inspec-reporter-test-fixture/lib/inspec-reporter-test-fixture/plugin.rb @@ -0,0 +1,13 @@ +require "inspec-reporter-test-fixture/version" + +module InspecPlugins + module ReporterTestFixture + class Plugin < ::Inspec.plugin(2) + plugin_name :'inspec-reporter-test-fixture' + reporter :"test-fixture" do + require "inspec-reporter-test-fixture/reporter" + InspecPlugins::ReporterTestFixture::ReporterImplementation + end + end + end +end diff --git a/test/fixtures/plugins/inspec-reporter-test-fixture/lib/inspec-reporter-test-fixture/reporter.rb b/test/fixtures/plugins/inspec-reporter-test-fixture/lib/inspec-reporter-test-fixture/reporter.rb new file mode 100644 index 000000000..e3669e0be --- /dev/null +++ b/test/fixtures/plugins/inspec-reporter-test-fixture/lib/inspec-reporter-test-fixture/reporter.rb @@ -0,0 +1,25 @@ +module InspecPlugins::ReporterTestFixture + class ReporterImplementation < Inspec.plugin(2, :reporter) + + # The test reporter plugin returns a single line of output, like this: + # pXX:cYY:tZZ + # where XX is the count of profiles + # YY is the count of controls + # ZZ is the count of tests + def render + profile_count = run_data[:profiles].count + control_count = 0 + test_count = 0 + run_data[:profiles].each do |p| + controls = p[:controls] || [] + control_count += controls.count + controls.each do |c| + tests = c[:results] || [] + test_count += tests.count + end + end + + output("p#{profile_count}c#{control_count}t#{test_count}",true) + end + end +end diff --git a/test/fixtures/plugins/inspec-reporter-test-fixture/lib/inspec-reporter-test-fixture/version.rb b/test/fixtures/plugins/inspec-reporter-test-fixture/lib/inspec-reporter-test-fixture/version.rb new file mode 100644 index 000000000..6e2b0d5a8 --- /dev/null +++ b/test/fixtures/plugins/inspec-reporter-test-fixture/lib/inspec-reporter-test-fixture/version.rb @@ -0,0 +1,5 @@ +module InspecPlugins + module ReporterTestFixture + VERSION = "0.1.0".freeze + end +end diff --git a/test/functional/plugins_test.rb b/test/functional/plugins_test.rb index 39bebb7d9..671f7d658 100644 --- a/test/functional/plugins_test.rb +++ b/test/functional/plugins_test.rb @@ -146,6 +146,28 @@ describe "input plugins" do end end +#=========================================================================================# +# Reporter plugin type +#=========================================================================================# +describe "reporter plugins" do + # The test reporter plugin returns a single line of output, like this: + # pXX:cYY:tZZ + # where XX is the count of profiles + # YY is the count of controls + # ZZ is the count of tests + let(:env) { { INSPEC_CONFIG_DIR: "#{config_dir_path}/reporter_plugin" } } + + # Test a flat profile - dependencies/profile_c is a simple one + describe "when using a custom reporter on a profile with one control" do + it "finds the single control" do + cmd = "exec #{profile_path}/dependencies/profile_c --reporter test-fixture" + run_result = run_inspec_process(cmd, env: env) + _(run_result.stderr).must_be_empty + _(run_result.stdout).must_include "p1c1t1" + end + end +end + #=========================================================================================# # inspec plugin command #=========================================================================================# diff --git a/test/unit/plugin/v2/api_reporter_test.rb b/test/unit/plugin/v2/api_reporter_test.rb new file mode 100644 index 000000000..0989d8d8f --- /dev/null +++ b/test/unit/plugin/v2/api_reporter_test.rb @@ -0,0 +1,39 @@ +require "helper" + +require "inspec/plugin/v2" + +describe "Reporter plugin type" do + describe "when registering the plugin type superclass" do + it "returns the superclass when calling the global defintion method" do + klass = Inspec.plugin(2, :reporter) + _(klass).must_be_kind_of Class + _(klass).must_equal Inspec::Plugin::V2::PluginType::Reporter + end + + it "returns the superclass when referenced by alias" do + klass = Inspec::Plugin::V2::PluginBase.base_class_for_type(:reporter) + _(klass).must_be_kind_of Class + _(klass).must_equal Inspec::Plugin::V2::PluginType::Reporter + end + + it "registers an activation dsl method" do + klass = Inspec::Plugin::V2::PluginBase + _(klass).must_respond_to :reporter + end + end + + describe "when examining the specific plugin type API" do + [ + # API instance methods + :render, # pure virtual + :output, # helper + :rendered_output, # accessor + :run_data, # accessor + ].each do |api_method| + it "should define a '#{api_method}' method in the superclass" do + klass = Inspec::Plugin::V2::PluginType::Reporter + _(klass.method_defined?(api_method)).must_equal true + end + end + end +end