From 50ff9f6a24cddd1ebe8e219f5bc6f85910ce37d9 Mon Sep 17 00:00:00 2001 From: Clinton Wolfe Date: Tue, 18 Sep 2018 00:00:54 -0400 Subject: [PATCH] Plugins: Add support for 'bundles' migration (#3384) Signed-off-by: Clinton Wolfe Signed-off-by: Christoph Hartmann Signed-off-by: Jared Quick --- Rakefile | 10 ++++- lib/inspec/plugin/v2/loader.rb | 40 ++++++++++++++--- lib/plugins/README.md | 16 +++++++ lib/plugins/shared/core_plugin_test_helper.rb | 45 +++++++++++++++++++ test/functional/plugins_test.rb | 2 +- test/helper.rb | 1 + test/unit/plugin/v2/loader_test.rb | 22 ++++++++- test/unit/reporters/automate_test.rb | 2 +- test/unit/resources/aws_route_tables_test.rb | 6 +-- 9 files changed, 128 insertions(+), 16 deletions(-) create mode 100644 lib/plugins/README.md create mode 100644 lib/plugins/shared/core_plugin_test_helper.rb diff --git a/Rakefile b/Rakefile index 9703c35f1..e64bf8bdf 100644 --- a/Rakefile +++ b/Rakefile @@ -54,7 +54,10 @@ task default: [:lint, :test] Rake::TestTask.new do |t| t.libs << 'test' - t.pattern = 'test/unit/**/*_test.rb' + t.test_files = Dir.glob([ + 'test/unit/**/*_test.rb', + 'lib/plugins/inspec-*/test/unit/**/*_test.rb', + ]) t.warning = true t.verbose = true t.ruby_opts = ['--dev'] if defined?(JRUBY_VERSION) @@ -69,7 +72,10 @@ namespace :test do Rake::TestTask.new(:functional) do |t| t.libs << 'test' - t.pattern = 'test/functional/**/*_test.rb' + t.test_files = Dir.glob([ + 'test/functional/**/*_test.rb', + 'lib/plugins/inspec-*/test/functional/**/*_test.rb', + ]) t.warning = true t.verbose = true t.ruby_opts = ['--dev'] if defined?(JRUBY_VERSION) diff --git a/lib/inspec/plugin/v2/loader.rb b/lib/inspec/plugin/v2/loader.rb index 7a182cada..34a58f8bd 100644 --- a/lib/inspec/plugin/v2/loader.rb +++ b/lib/inspec/plugin/v2/loader.rb @@ -17,7 +17,14 @@ module Inspec::Plugin::V2 determine_plugin_conf_file read_conf_file unpack_conf_file + + # Old-style (v0, v1) co-distributed plugins were called 'bundles' + # and were located in lib/bundles detect_bundled_plugins unless options[:omit_bundles] + + # New-style (v2) co-distributed plugins are in lib/plugins, + # and may be safely loaded + detect_core_plugins unless options[:omit_core_plugins] end def load_all @@ -26,17 +33,20 @@ module Inspec::Plugin::V2 # rubocop: disable Lint/RescueException begin # We could use require, but under testing, we need to repeatedly reload the same - # plugin. - if plugin_details.entry_point.include?('test/unit/mock/plugins') - load plugin_details.entry_point + '.rb' - else + # plugin. However, gems only work with require (rubygems dooes not overload `load`) + if plugin_details.installation_type == :gem + activate_managed_gems_for_plugin(plugin_name) require plugin_details.entry_point + else + load_path = plugin_details.entry_point + load_path += '.rb' unless plugin_details.entry_point.end_with?('.rb') + load load_path end plugin_details.loaded = true annotate_status_after_loading(plugin_name) rescue ::Exception => ex plugin_details.load_exception = ex - Inspec::Log.error "Could not load plugin #{plugin_name}" + Inspec::Log.error "Could not load plugin #{plugin_name}: #{ex.message}" end # rubocop: enable Lint/RescueException end @@ -60,7 +70,7 @@ module Inspec::Plugin::V2 end def activate(plugin_type, hook_name) - activator = registry.find_activators(plugin_type: plugin_type, activation_name: hook_name).first + activator = registry.find_activators(plugin_type: plugin_type, activator_name: hook_name).first # We want to capture literally any possible exception here, since we are storing them. # rubocop: disable Lint/RescueException begin @@ -80,7 +90,7 @@ module Inspec::Plugin::V2 next if act.activated # If there is anything in the CLI args with the same name, activate it # If the word 'help' appears in the first position, load all CLI plugins - if cli_args.include?(act.activator_name.to_s) || cli_args[0] == 'help' + if cli_args.include?(act.activator_name.to_s) || cli_args[0] == 'help' || cli_args.size.empty? activate(:cli_command, act.activator_name) act.implementation_class.register_with_thor end @@ -138,6 +148,22 @@ module Inspec::Plugin::V2 @plugin_conf_file_path = File.join(@plugin_conf_file_path, 'plugins.json') end + def detect_core_plugins + core_plugins_dir = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', '..', 'plugins')) + # These are expected to be organized as proper separate projects, + # with lib/ dirs, etc. + Dir.glob(File.join(core_plugins_dir, 'inspec-*')).each do |plugin_dir| + status = Inspec::Plugin::V2::Status.new + status.name = File.basename(plugin_dir) + status.entry_point = File.join(plugin_dir, 'lib', status.name + '.rb') + status.installation_type = :path + status.loaded = false + registry[status.name.to_sym] = status + end + end + + # TODO: DRY up re: Installer read_or_init_config_file + # TODO: refactor the plugin.json file to have its own class, which Loader consumes def read_conf_file if File.exist?(@plugin_conf_file_path) @plugin_file_contents = JSON.parse(File.read(@plugin_conf_file_path)) diff --git a/lib/plugins/README.md b/lib/plugins/README.md new file mode 100644 index 000000000..fa16fa9d0 --- /dev/null +++ b/lib/plugins/README.md @@ -0,0 +1,16 @@ +# Core Plugins of InSpec + +This area contains the plugins that ship with InSpec. They are automatically detected by the plugin loader. + +## inspec-* directories + +Each subdirectory here that begins with `inspec-` is intended to be a self-contained plugin project, +with code, docs, and tests. + +## shared directory + +This directory contains material that is reusable for core plugins, such as a test helper tuned to assisting +core plugin testing. + + + diff --git a/lib/plugins/shared/core_plugin_test_helper.rb b/lib/plugins/shared/core_plugin_test_helper.rb new file mode 100644 index 000000000..a4a0c065f --- /dev/null +++ b/lib/plugins/shared/core_plugin_test_helper.rb @@ -0,0 +1,45 @@ + +# Load test harness - MiniTest +require 'minitest/autorun' +require 'minitest/unit' +require 'minitest/pride' +require 'minitest/spec' + +# Data formats commonly used in testing +require 'json' +require 'ostruct' + +# Utilities often needed +require 'fileutils' + +# Configure MiniTest to expose things like `let` +class Module + include Minitest::Spec::DSL +end + +module CorePluginBaseHelper + let(:repo_path) { File.expand_path(File.join(__FILE__, '..', '..', '..', '..')) } + let(:inspec_bin_path) { File.join(repo_path, 'bin', 'inspec') } + let(:core_mock_path) { File.join(repo_path, 'test', 'unit', 'mock') } + let(:core_fixture_plugins_path) { File.join(core_mock_path, 'plugins') } + let(:core_config_dir_path) { File.join(core_mock_path, 'config_dirs') } + + let(:registry) { Inspec::Plugin::V2::Registry.instance } +end + +module CorePluginFunctionalHelper + include CorePluginBaseHelper + + require 'train' + TRAIN_CONNECTION = Train.create('local', command_runner: :generic).connection + + def run_inspec_process(command_line, env = {}) + env_prefix = env.to_a.map { |assignment| "#{assignment[0]}=#{assignment[1]}" }.join(' ') + TRAIN_CONNECTION.run_command("#{env_prefix} #{inspec_bin_path} #{command_line}") + end +end + +module CorePluginUnitHelper + include CorePluginBaseHelper + require 'inspec' +end diff --git a/test/functional/plugins_test.rb b/test/functional/plugins_test.rb index d018e4528..c0ab3b9fc 100644 --- a/test/functional/plugins_test.rb +++ b/test/functional/plugins_test.rb @@ -79,4 +79,4 @@ describe 'cli command plugins' do outcome.exit_status.must_equal 0 outcome.stdout.must_include 'inspec meaningoflife' end -end \ No newline at end of file +end diff --git a/test/helper.rb b/test/helper.rb index acc5317f4..f489169c6 100644 --- a/test/helper.rb +++ b/test/helper.rb @@ -26,6 +26,7 @@ require 'inspec/exceptions' require 'inspec/fetcher' require 'inspec/source_reader' require 'inspec/resource' +require 'resource_support/aws' require 'inspec/reporters' require 'inspec/backend' require 'inspec/profile' diff --git a/test/unit/plugin/v2/loader_test.rb b/test/unit/plugin/v2/loader_test.rb index 96575c0c4..7fa6cf0cc 100644 --- a/test/unit/plugin/v2/loader_test.rb +++ b/test/unit/plugin/v2/loader_test.rb @@ -29,6 +29,8 @@ class PluginLoaderTests < MiniTest::Test :init, :supermarket, ] + @core_plugins = [ + ] end def teardown @@ -53,6 +55,14 @@ class PluginLoaderTests < MiniTest::Test end end + def test_constructor_should_detect_core_plugins + reg = Inspec::Plugin::V2::Registry.instance + loader = Inspec::Plugin::V2::Loader.new + @core_plugins.each do |core_plugin_name| + assert reg.known_plugin?(core_plugin_name), "\n#{core_plugin_name} should be detected as a core plugin" + end + end + def test_constructor_should_skip_bundles_when_option_is_set reg = Inspec::Plugin::V2::Registry.instance loader = Inspec::Plugin::V2::Loader.new(omit_bundles: true) @@ -61,6 +71,14 @@ class PluginLoaderTests < MiniTest::Test end end + def test_constructor_should_skip_core_when_option_is_set + reg = Inspec::Plugin::V2::Registry.instance + loader = Inspec::Plugin::V2::Loader.new(omit_core_plugins: true) + @core_plugins.each do |core_plugin_name| + refute reg.known_plugin?(core_plugin_name), "\n#{core_plugin_name} should not be detected when omit_core_plugins is set" + end + end + def test_constructor_when_using_home_dir_detects_declared_plugins ENV['HOME'] = File.join(@config_dir_path, 'fakehome') reg = Inspec::Plugin::V2::Registry.instance @@ -97,7 +115,7 @@ class PluginLoaderTests < MiniTest::Test def test_load_no_plugins_should_load_no_plugins reg = Inspec::Plugin::V2::Registry.instance - loader = Inspec::Plugin::V2::Loader.new(omit_bundles: true) + loader = Inspec::Plugin::V2::Loader.new(omit_bundles: true, omit_core_plugins: true) loader.load_all assert_equal 0, reg.loaded_count, "\nRegistry load count" end @@ -166,7 +184,7 @@ class PluginLoaderTests < MiniTest::Test assert_nil activator.implementation_class, 'Test activator should not know implementation class prior to activation' refute InspecPlugins::MeaningOfLife.const_defined?(:MockPlugin), 'impl_class should not be defined prior to activation' - loader.activate(:mock_plugin_type, 'meaning-of-life-the-universe-and-everything') + loader.activate(:mock_plugin_type, :'meaning-of-life-the-universe-and-everything') # Activation postconditions assert activator.activated, 'Test activator should be activated after activate' diff --git a/test/unit/reporters/automate_test.rb b/test/unit/reporters/automate_test.rb index a41cd412a..eb0974cd8 100644 --- a/test/unit/reporters/automate_test.rb +++ b/test/unit/reporters/automate_test.rb @@ -40,7 +40,7 @@ describe Inspec::Reporters::Automate do } stub = Net::HTTP::Post.new("/data-collector/v0/", headers) Net::HTTP::Post.expects(:new).with("/data-collector/v0/", headers).returns(stub) - Net::HTTP.any_instance.stubs(:request).returns(true) + Net::HTTP.any_instance.stubs(:request).returns(Net::HTTPSuccess.new(nil, nil, nil)) report.send_report.must_equal true end end diff --git a/test/unit/resources/aws_route_tables_test.rb b/test/unit/resources/aws_route_tables_test.rb index 2c4b7eb63..ab0942ff2 100644 --- a/test/unit/resources/aws_route_tables_test.rb +++ b/test/unit/resources/aws_route_tables_test.rb @@ -2,7 +2,7 @@ require 'helper' class EmptyAwsRouteTablesTest < Minitest::Test def setup - AwsRouteTables::BackendFactory.select(AwsMRtbB::Empty) + AwsRouteTables::BackendFactory.select(AwsMRtbsB::Empty) end def test_constructor_no_args_ok @@ -20,7 +20,7 @@ end class BasicAwsRouteTablesTest2 < Minitest::Test def setup - AwsRouteTables::BackendFactory.select(AwsMRtbB::Basic) + AwsRouteTables::BackendFactory.select(AwsMRtbsB::Basic) end def test_search_hit @@ -45,7 +45,7 @@ class BasicAwsRouteTablesTest2 < Minitest::Test end # MRtbB = Mock Routetable Backend -module AwsMRtbB +module AwsMRtbsB class Empty < AwsBackendBase def describe_route_tables(query) OpenStruct.new(route_tables: [])