Merge pull request #5936 from inspec/nm/plugin-search-list-description

CFINSPEC-118 Description added in plugin list and search command
This commit is contained in:
Clinton Wolfe 2022-03-23 09:48:36 -04:00 committed by GitHub
commit 22f8e1a6e7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 188 additions and 47 deletions

View file

@ -149,12 +149,19 @@ module Inspec::Plugin::V2
gem_info = {}
matched_tuples.each do |tuple|
gem_info[tuple.first.name] ||= []
gem_info[tuple.first.name] << tuple.first.version.to_s
gem_info[tuple.first.name] ||= {}
gem_info[tuple.first.name]["versions"] ||= []
gem_info[tuple.first.name]["versions"] << tuple.first.version.to_s
gem_info[tuple.first.name]["description"] ||= fetch_plugin_specs(fetcher, tuple.first.name)&.summary
end
gem_info
end
def fetch_plugin_specs(fetcher, gem_name)
plugin_dependency = Gem::Dependency.new(gem_name)
fetcher.spec_for_dependency(plugin_dependency).flatten.first
end
# Testing API. Performs a hard reset on the installer and registry, and reloads the loader.
# Not for public use.
# TODO: bad timing coupling in tests

View file

@ -259,10 +259,15 @@ module Inspec::Plugin::V2
status.entry_point = File.join(plugin_dir, "lib", status.name.to_s + ".rb")
status.installation_type = :core
status.loaded = false
status.description = fetch_gemspec(File.join(plugin_dir, status.name.to_s + ".gemspec"))&.summary
registry[status.name.to_sym] = status
end
end
def fetch_gemspec(spec_file)
Gem::Specification.load(spec_file)
end
def read_conf_file_into_registry
conf_file.each do |plugin_entry|
status = Inspec::Plugin::V2::Status.new
@ -273,6 +278,7 @@ module Inspec::Plugin::V2
when :user_gem
status.entry_point = status.name.to_s
status.version = plugin_entry[:version]
status.description = fetch_plugin_specs(status.name.to_s)&.summary
when :path
status.entry_point = plugin_entry[:installation_path]
end
@ -281,6 +287,12 @@ module Inspec::Plugin::V2
end
end
def fetch_plugin_specs(plugin_name)
fetcher = Gem::SpecFetcher.fetcher
plugin_dependency = Gem::Dependency.new(plugin_name)
fetcher.spec_for_dependency(plugin_dependency).flatten.first
end
def fixup_train_plugin_status(status)
status.api_generation = :'train-1'
if status.installation_type == :user_gem
@ -327,6 +339,7 @@ module Inspec::Plugin::V2
status.version = plugin_spec.version.to_s
status.loaded = false
status.installation_type = :system_gem
status.description = plugin_spec.summary
if train_plugin_name?(status[:name])
# Train plugins are not true InSpec plugins; we need to decorate them a

View file

@ -14,7 +14,8 @@ module Inspec::Plugin::V2
:loaded, # true, false False could mean not attempted or failed
:load_exception, # Exception class if it failed to load
:name, # String name
:version # three-digit version. Core / bundled plugins use InSpec version here.
:version, # three-digit version. Core / bundled plugins use InSpec version here.
:description # Description of plugin.
) do
def initialize(*)
super

View file

@ -140,6 +140,15 @@ module Inspec
print_or_return(result, opts[:print])
end
def line_with_width(width = 80, opts = { print: true } )
if color?
result = ANSI_CODES[:bold] + GLYPHS[:heavy_dash] * width + ANSI_CODES[:reset] + "\n"
else
result = "-" * width + "\n"
end
print_or_return(result, opts[:print])
end
# Makes a bullet point.
def list_item(str, opts = { print: true })
bullet = color? ? ANSI_CODES[:bold] + ANSI_CODES[:color][:white] + GLYPHS[:bullet] + ANSI_CODES[:reset] : "*"

View file

@ -0,0 +1,9 @@
# .gemspec file is added to add plugin details
# These specs are used in plugin list and search command
Gem::Specification.new do |spec|
spec.name = "inspec-artifact"
spec.summary = ""
spec.description = "Plugin to generate asymmetrical keys that you can use to encrypt profiles"
spec.license = "Apache-2.0"
end

View file

@ -0,0 +1,9 @@
# .gemspec file is added to add plugin details
# These specs are used in plugin list and search command
Gem::Specification.new do |spec|
spec.name = "inspec-compliance"
spec.summary = "Plugin to perform operations with Chef Automate"
spec.description = "This extensions will allow you to interact with Chef Automate"
spec.license = "Apache-2.0"
end

View file

@ -0,0 +1,9 @@
# .gemspec file is added to add plugin details
# These specs are used in plugin list and search command
Gem::Specification.new do |spec|
spec.name = "inspec-habitat"
spec.summary = "Plugin to create/upload habitat package"
spec.description = "This extensions will allow you to create/upload habitat package from an inspec profile."
spec.license = "Apache-2.0"
end

View file

@ -0,0 +1,9 @@
# .gemspec file is added to add plugin details
# These specs are used in plugin list and search command
Gem::Specification.new do |spec|
spec.name = "inspec-init"
spec.summary = "Plugin for scaffolding profile, plugin or a resource"
spec.description = "This extensions helps you to easily create a new profile, plugin or a resource."
spec.license = "Apache-2.0"
end

View file

@ -0,0 +1,10 @@
# .gemspec file is added to add plugin details
# These specs are used in plugin list and search command
Gem::Specification.new do |spec|
spec.name = "inspec-plugin-manager-cli"
spec.summary = "CLI plugin for InSpec"
spec.description = "This is a CLI plugin for InSpec. It uses the Plugins API v2 to create a
series of commands to manage plugins."
spec.license = "Apache-2.0"
end

View file

@ -41,13 +41,14 @@ module InspecPlugins
unless plugin_statuses.empty?
ui.table do |t|
t.header = ["Plugin Name", "Version", "Via", "ApiVer"]
t.header = ["Plugin Name", "Version", "Via", "ApiVer", "Description"]
plugin_statuses.sort_by { |s| s.name.to_s }.each do |status|
t << [
status.name,
make_pretty_version(status),
make_pretty_install_type(status),
status.api_generation,
status.description,
]
end
end
@ -83,14 +84,15 @@ module InspecPlugins
end
puts
ui.bold(format(" %-30s%-50s\n", "Plugin Name", "Versions Available"))
ui.line
ui.bold(format(" %-30s%-30s%-20s\n", "Plugin Name", "Versions Available", "Description"))
ui.line_with_width(100)
search_results.keys.sort.each do |plugin_name|
versions = options[:all] ? search_results[plugin_name] : [search_results[plugin_name].first]
versions = options[:all] ? search_results[plugin_name]["versions"] : [search_results[plugin_name]["versions"].first]
versions = "(" + versions.join(", ") + ")"
ui.plain_line(format(" %-30s%-50s", plugin_name, versions))
description = search_results[plugin_name]["description"]
ui.plain_line(format(" %-30s%-30s%-20s", plugin_name, versions, description))
end
ui.line
ui.line_with_width(100)
ui.plain_line(" #{search_results.count} plugin(s) found")
puts
@ -381,9 +383,11 @@ module InspecPlugins
# Do an expensive search to determine the latest version.
unless requested_version
latest_version = installer.search(plugin_name, exact: true, scope: :latest)
latest_version = latest_version[plugin_name]&.last
if latest_version && !requested_version
requested_version = latest_version
if latest_version[plugin_name]
latest_version = latest_version[plugin_name]["versions"]&.last
if latest_version && !requested_version
requested_version = latest_version
end
end
end
@ -429,7 +433,7 @@ module InspecPlugins
if results.empty?
ui.red("No such plugin gem #{plugin_name} could be found on " \
"#{source_host} - installation failed.\n")
elsif options[:version] && !results[plugin_name].include?(options[:version])
elsif options[:version] && results[plugin_name] && !results[plugin_name]["versions"].include?(options[:version])
ui.red("No such version - #{plugin_name} exists, but no such " \
"version #{options[:version]} found on #{source_host} - " \
"installation failed.\n")
@ -460,7 +464,7 @@ module InspecPlugins
# Check for latest version (and implicitly, existence)
latest_version = installer.search(plugin_name, exact: true, scope: :latest)
latest_version = latest_version[plugin_name]&.last
latest_version = latest_version[plugin_name] ? latest_version[plugin_name]["versions"]&.last : nil
if pre_update_versions.include?(latest_version)
ui.plain_line("#{ui.bold("Already installed at latest version:", print: false)} " \

View file

@ -9,8 +9,9 @@ module PluginManagerHelpers
let(:list_after_run) do
Proc.new do |run_result, tmp_dir|
# After installing/uninstalling/whatevering, run list with config in the same dir, and capture it.
result = run_inspec_process("plugin list", env: { INSPEC_CONFIG_DIR: tmp_dir })
@list_result = parse_plugin_list_lines(
run_inspec_process("plugin list", env: { INSPEC_CONFIG_DIR: tmp_dir }).stdout
result.stdout, result.stderr
)
end
end
@ -35,8 +36,9 @@ module PluginManagerHelpers
end
end
def parse_plugin_list_lines(stdout)
def parse_plugin_list_lines(stdout, stderr = "")
plugins = []
plugin_data = {}
stdout.force_encoding("UTF-8").lines.each do |line|
next if line.strip.empty?
@ -45,13 +47,31 @@ module PluginManagerHelpers
next if line.include? "plugin(s) total"
parts = line.split(//u).map(&:strip!).compact
plugins << {
name: parts[0],
version: parts[1],
type: parts[2],
generation: parts[3],
raw: line,
}
if stderr.match(/vertical orientation/)
# logic to parse data from vertical view if the ui breaks in existing width
if line.match(/Plugin/)
plugin_data = {} # reset this again when one row in vertical view is parsed
plugin_data[:name] = line.split(" ")[-2]
elsif line.match(/Version/)
plugin_data[:version] = parts[1]
elsif line.match(/Via/)
plugin_data[:type] = parts[1]
elsif line.match(/ApiVer/)
plugin_data[:generation] = parts[1]
elsif line.match(/Descrip/)
plugin_data[:description] = parts[1]
plugins << plugin_data # assuming this will be end of row
end
else
plugins << {
name: parts[0],
version: parts[1],
type: parts[2],
generation: parts[3],
description: parts[4],
raw: line,
}
end
end
plugins
end

View file

@ -6,22 +6,20 @@ class PluginManagerCliList < Minitest::Test
# Listing all plugins is now default behavior
LIST_CASES = [
{ arg: "-c", name: "inspec-plugin-manager-cli", type: "core" },
{ arg: "-c", name: "inspec-supermarket", type: "core" },
{ arg: "-s", name: "train-aws", type: "gem (system)" },
{ arg: "-c", name: "inspec-plugin-manager-cli", type: "core", description: "CLI plugin for InSpec" },
{ arg: "-c", name: "inspec-supermarket", type: "core", description: "" },
{ arg: "-s", name: "train-aws", type: "gem (system)", description: "AWS API Transport for Train" },
].freeze
def test_list_all_when_no_user_plugins_installed
result = run_inspec_process_with_this_plugin("plugin list --all")
assert_empty result.stderr
plugins_seen = parse_plugin_list_lines(result.stdout)
plugins_seen = parse_plugin_list_lines(result.stdout, result.stderr)
# Look for a specific plugin of each type - core, bundle, and system
LIST_CASES.each do |test_case|
plugin_line = plugins_seen.detect { |plugin| plugin[:name] == test_case[:name] }
refute_nil plugin_line, "#{test_case[:name]} should be detected in plugin list --all output"
assert_equal test_case[:type], plugin_line[:type], "#{test_case[:name]} should be detected as a '#{test_case[:type]}' type in list --all "
assert_equal test_case[:description], plugin_line[:description], "#{test_case[:name]} should have description '#{test_case[:description]}' in list --all "
end
assert_exit_code 0, result
end
@ -29,12 +27,11 @@ class PluginManagerCliList < Minitest::Test
def test_list_selective_when_no_user_plugins_installed
LIST_CASES.each do |test_case|
result = run_inspec_process_with_this_plugin("plugin list #{test_case[:arg]}")
assert_empty result.stderr
plugins_seen = parse_plugin_list_lines(result.stdout)
plugins_seen = parse_plugin_list_lines(result.stdout, result.stderr)
plugin_line = plugins_seen.detect { |plugin| plugin[:name] == test_case[:name] }
refute_nil plugin_line, "#{test_case[:name]} should be detected in plugin list #{test_case[:arg]} output"
assert_equal plugin_line[:type], test_case[:type], "#{test_case[:name]} should be detected as a '#{test_case[:type]}' type in list #{test_case[:arg]} "
assert_equal test_case[:description], plugin_line[:description], "#{test_case[:name]} should have description '#{test_case[:description]}' in list --all "
assert_exit_code 0, result
end
end
@ -47,14 +44,13 @@ class PluginManagerCliList < Minitest::Test
result = run_inspec_process_with_this_plugin("plugin list --user ", pre_run: pre_block)
assert_empty result.stderr
plugins_seen = parse_plugin_list_lines(result.stdout)
plugins_seen = parse_plugin_list_lines(result.stdout, result.stderr)
assert_equal 2, plugins_seen.count
# Plugin Name Version Via ApiVer
# ---------------------------------------------------------
# Plugin Name Version Via ApiVer Description
# -----------------------------------------------------------------------------
# inspec-meaning-of-life src path 2
# inspec-test-fixture 0.1.0 gem (user) 2
# ---------------------------------------------------------
# inspec-test-fixture 0.1.0 gem (user) 2 A simple test plugin gem for InSpec
# -----------------------------------------------------------------------------
# 2 plugin(s) total
meaning = plugins_seen.detect { |p| p[:name] == "inspec-meaning-of-life" }
refute_nil meaning
@ -64,6 +60,7 @@ class PluginManagerCliList < Minitest::Test
refute_nil fixture
assert_equal "gem (user)", fixture[:type]
assert_equal "0.1.0", fixture[:version]
assert_match(/A simple test plugin gem/, fixture[:description])
assert_exit_code 0, result
end
@ -76,22 +73,21 @@ class PluginManagerCliList < Minitest::Test
result = run_inspec_process_with_this_plugin("plugin list --user ", pre_run: pre_block)
assert_empty result.stderr
plugins_seen = parse_plugin_list_lines(result.stdout)
plugins_seen = parse_plugin_list_lines(result.stdout, result.stderr)
assert_equal 1, plugins_seen.count
assert_includes result.stdout, "1 plugin(s) total", "list train should show one plugins"
# Plugin Name Version Via ApiVer
# -------------------------------------------------------------
# train-test-fixture 0.1.0 gem (user) train-1
# -------------------------------------------------------------
# Plugin Name Version Via ApiVer Description
# -------------------------------------------------------------------------------
# train-test-fixture 0.1.0 gem (user) train-1 Test train plugin. Not intended for use as an example
# -------------------------------------------------------------------------------
# 1 plugin(s) total
train_plugin = plugins_seen.detect { |p| p[:name] == "train-test-fixture" }
refute_nil train_plugin
assert_equal "gem (user)", train_plugin[:type]
assert_equal "train-1", train_plugin[:generation]
assert_equal "0.1.0", train_plugin[:version]
assert_match(/Test train plugin/, train_plugin[:description])
assert_exit_code 0, result
end
end

View file

@ -14,8 +14,8 @@ class PluginManagerCliSearch < Minitest::Test
def test_search_for_a_real_gem_with_full_name_no_options
result = run_inspec_process("plugin search --include-test-fixture inspec-test-fixture")
assert_includes result.stdout, "inspec-test-fixture", "Search result should contain the gem name"
assert_includes result.stdout, "A simple test plugin gem for InSpec", "Search result should contain the gem description"
assert_includes result.stdout, "1 plugin(s) found", "Search result should find 1 plugin"
line = result.stdout.split("\n").grep(/inspec-test-fixture/).first
assert_match(/\s*inspec-test-fixture\s+\((\d+\.\d+\.\d+){1}\)/, line, "Plugin line should include name and exactly one version")
@ -27,6 +27,7 @@ class PluginManagerCliSearch < Minitest::Test
result = run_inspec_process("plugin search --include-test-fixture inspec-test-")
assert_includes result.stdout, "inspec-test-fixture", "Search result should contain the gem name"
assert_includes result.stdout, "A simple test plugin gem for InSpec", "Search result should contain the gem description"
assert_includes result.stdout, "1 plugin(s) found", "Search result should find 1 plugin"
line = result.stdout.split("\n").grep(/inspec-test-fixture/).first
@ -39,6 +40,7 @@ class PluginManagerCliSearch < Minitest::Test
result = run_inspec_process("plugin search --exact --include-test-fixture inspec-test-fixture")
assert_includes result.stdout, "inspec-test-fixture", "Search result should contain the gem name"
assert_includes result.stdout, "A simple test plugin gem for InSpec", "Search result should contain the gem description"
assert_includes result.stdout, "1 plugin(s) found", "Search result should find 1 plugin"
assert_exit_code 0, result
@ -65,6 +67,7 @@ class PluginManagerCliSearch < Minitest::Test
def test_search_for_a_real_gem_with_full_name_and_all_option
result = run_inspec_process("plugin search --all --include-test-fixture inspec-test-fixture")
assert_includes result.stdout, "inspec-test-fixture", "Search result should contain the gem name"
assert_includes result.stdout, "A simple test plugin gem for InSpec", "Search result should contain the gem description"
assert_includes result.stdout, "1 plugin(s) found", "Search result should find 1 plugin"
line = result.stdout.split("\n").grep(/inspec-test-fixture/).first
@ -96,6 +99,7 @@ class PluginManagerCliSearch < Minitest::Test
result = run_inspec_process("plugin search --include-test-fixture train-test-fixture")
assert_includes result.stdout, "train-test-fixture", "Search result should contain the gem name"
assert_includes result.stdout, "Test train plugin. Not intended for use as an example", "Search result should contain the gem description"
assert_includes result.stdout, "1 plugin(s) found", "Search result should find 1 plugin"
line = result.stdout.split("\n").grep(/train-test-fixture/).first
assert_match(/\s*train-test-fixture\s+\((\d+\.\d+\.\d+){1}\)/, line, "Plugin line should include name and exactly one version")
@ -120,10 +124,12 @@ class PluginManagerCliSearch < Minitest::Test
def test_search_for_a_real_gem_with_full_name_no_options_filter_fixtures
result = run_inspec_process("plugin search inspec-test-fixture")
refute_includes result.stdout, "inspec-test-fixture", "Search result should not contain the fixture gem name"
refute_includes result.stdout, "Test train plugin. Not intended for use as an example", "Search result should not contain the gem description"
end
def test_search_for_a_real_gem_with_full_name_no_options_filter_fixtures_train
result = run_inspec_process("plugin search train-test-fixture")
refute_includes result.stdout, "train-test-fixture", "Search result should not contain the fixture gem name"
refute_includes result.stdout, "Test train plugin. Not intended for use as an example", "Search result should not contain the gem description"
end
end

View file

@ -0,0 +1,9 @@
# .gemspec file is added to add plugin details
# These specs are used in plugin list and search command
Gem::Specification.new do |spec|
spec.name = "inspec-reporter-html2"
spec.summary = "Improved HTML reporter plugin"
spec.description = "An improved HTML output reporter specifically for Chef InSpec. Unlike the default html reporter, which is RSpec-based, this reporter knows about Chef InSpec structures like Controls and Profiles, and includes full metadata such as control tags, etc."
spec.license = "Apache-2.0"
end

View file

@ -0,0 +1,9 @@
# .gemspec file is added to add plugin details
# These specs are used in plugin list and search command
Gem::Specification.new do |spec|
spec.name = "inspec-reporter-json-min"
spec.summary = "Json-min json reporter plugin"
spec.description = "This plugin provides the `json-min` reporter, which produces test output in JSON format with less detail than the `json` reporter."
spec.license = "Apache-2.0"
end

View file

@ -0,0 +1,9 @@
# .gemspec file is added to add plugin details
# These specs are used in plugin list and search command
Gem::Specification.new do |spec|
spec.name = "inspec-reporter-junit"
spec.summary = "JUnit XML reporter plugin"
spec.description = "`junit` is the legacy Chef InSpec JUnit reporter, which is retained for backwards compatibility. It generates an XML report in Apache Ant JUnit format. The output format is considered nonstandard in several ways. New users are advised to use `junit2`."
spec.license = "Apache-2.0"
end

View file

@ -0,0 +1,9 @@
# .gemspec file is added to add plugin details
# These specs are used in plugin list and search command
Gem::Specification.new do |spec|
spec.name = "inspec-streaming-reporter-progress-bar"
spec.summary = "Displays a real-time progress bar and control title as output"
spec.description = "This plugin is a streaming reporter plugin which shows the real-time progress of a running InSpec profile using a progress bar. It also outputs the ID of a running control with an indicator showing if the control has passed, failed or skipped."
spec.license = "Apache-2.0"
end

View file

@ -424,6 +424,7 @@ class PluginInstallerUninstallTests < Minitest::Test
end
def test_uninstall_a_gem_plugin
WebMock.disable_net_connect!(allow: %r{(api\.)?rubygems\.org/.*})
skip_slow_tests # not that slow, just noisy
copy_in_config_dir("test-fixture-1-float")
@ -451,6 +452,7 @@ class PluginInstallerUninstallTests < Minitest::Test
end
def test_uninstall_a_gem_plugin_removes_deps
WebMock.disable_net_connect!(allow: %r{(api\.)?rubygems\.org/.*})
skip_slow_tests # not that slow, just noisy
copy_in_config_dir("test-fixture-2-float")

View file

@ -25,6 +25,7 @@ class PluginLoaderTests < Minitest::Test
end
def setup
WebMock.disable_net_connect!(allow: %r{(api\.)?rubygems\.org/.*})
reset_globals
@config_dir_path = File.expand_path "test/fixtures/config_dirs"