mirror of
https://github.com/inspec/inspec
synced 2024-11-10 15:14:23 +00:00
CLI Plugin Manager SubCommand (#3414)
Signed-off-by: Clinton Wolfe <clintoncwolfe@gmail.com>
This commit is contained in:
parent
76a688a747
commit
0ced18841f
27 changed files with 1312 additions and 24 deletions
|
@ -5,9 +5,9 @@ AllCops:
|
|||
- Gemfile
|
||||
- Rakefile
|
||||
- 'test/**/*'
|
||||
- 'lib/plugins/*/test/**/*'
|
||||
- 'examples/**/*'
|
||||
- 'vendor/**/*'
|
||||
- 'lib/plugins/inspec-*/test/**/*'
|
||||
- 'lib/bundles/inspec-init/templates/**/*'
|
||||
- 'www/demo/**/*'
|
||||
AlignParameters:
|
||||
|
|
|
@ -26,9 +26,11 @@ The software design of the InSpec Plugin v2 API is deeply inspired by the Vagran
|
|||
|
||||
The normal distribution and installation method is via gems, handled by the `inspec plugin` command.
|
||||
|
||||
TODO: give basic overview of `inspec plugin` and link to docs
|
||||
`inspec plugin install inspec-myplugin` will fetch `inspec-myplugin` from rubygems.org, and install it and its gemspec dependencies under the user's `.inspec` directory. You may also provide a local gemfile. For local development, however, path-to-source is usually most convenient.
|
||||
|
||||
### Plugins may also be found by path
|
||||
For more on the `plugin` CLI command, run `inspec plugin help`.
|
||||
|
||||
### Plugins may also be found by path to a source tree
|
||||
|
||||
For local development or site-specific installations, you can also 'install' a plugin by path using `inspec plugin`, or edit `~/.inspec/plugins.json` directly to add a plugin.
|
||||
|
||||
|
|
46
docs/plugins.md
Normal file
46
docs/plugins.md
Normal file
|
@ -0,0 +1,46 @@
|
|||
---
|
||||
title: About InSpec Plugins
|
||||
---
|
||||
|
||||
# InSpec Plugins
|
||||
|
||||
## What are InSpec Plugins?
|
||||
|
||||
Plugins are optional software components that extend the capabilities of InSpec. For example, `inspec-iggy` is a plugin project that aims to generate InSpec controls from infrastructure-as-code. Plugins are distributed as RubyGems, and InSpec manages their installation.
|
||||
|
||||
## What can plugins do?
|
||||
|
||||
Currently, each plugin can offer one or more of these capabilities:
|
||||
|
||||
* define a new command-line-interface (CLI) command suite
|
||||
|
||||
Future work might include new capability types, such as:
|
||||
|
||||
* connectivity to new types of hosts or cloud providers (`train` transports)
|
||||
* reporters (output generators)
|
||||
* DSL extensions at the file, control, or test level
|
||||
* attribute fetchers to allow reading InSpec attributes from new sources (for example, a remote, encrypted key-value store)
|
||||
|
||||
## How do I find out which plugins are available?
|
||||
|
||||
The InSpec CLI can tell you which plugins are available:
|
||||
|
||||
```bash
|
||||
$ inspec plugin search
|
||||
```
|
||||
|
||||
## How do I install and manage plugins?
|
||||
|
||||
The InSpec command line now offers a new subcommand just for managing plugins.
|
||||
|
||||
You can install a plugin by running:
|
||||
|
||||
```bash
|
||||
$ inspec plugin install inspec-some-plugin
|
||||
```
|
||||
|
||||
For more details on what the `plugin` command can do, see the [online help](https://www.inspec.io/docs/reference/cli/#plugin), or run `inspec plugin help`.
|
||||
|
||||
## How do I write a plugin?
|
||||
|
||||
For details on how to author a Plugin, see the [developer documentation](https://github.com/inspec/inspec/blob/master/docs/dev/plugins.md)
|
|
@ -47,4 +47,5 @@ Gem::Specification.new do |spec|
|
|||
spec.add_dependency 'semverse'
|
||||
spec.add_dependency 'htmlentities'
|
||||
spec.add_dependency 'multipart-post'
|
||||
spec.add_dependency 'term-ansicolor'
|
||||
end
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
require 'singleton'
|
||||
require 'forwardable'
|
||||
require 'fileutils'
|
||||
|
||||
# Gem extensions for doing unusual things - not loaded by Gem default
|
||||
require 'rubygems/package'
|
||||
|
@ -114,6 +115,7 @@ module Inspec::Plugin::V2
|
|||
# @param [String] plugin_seach_term
|
||||
# @param [Hash] opts Search options
|
||||
# @option opts [TrueClass, FalseClass] :exact If true, use plugin_search_term exactly. If false (default), append a wildcard.
|
||||
# @option opts [Symbol] :scope Which versions to search for. :released (default) - all released versions. :prerelease - Also include versioned marked prerelease. :latest - only return one version, the latest one.
|
||||
# @return [Hash of Arrays] - Keys are String names of gems, arrays contain String versions.
|
||||
def search(plugin_query, opts = {})
|
||||
validate_search_opts(plugin_query, opts)
|
||||
|
@ -121,10 +123,10 @@ module Inspec::Plugin::V2
|
|||
fetcher = Gem::SpecFetcher.fetcher
|
||||
matched_tuples = []
|
||||
if opts[:exact]
|
||||
matched_tuples = fetcher.detect(:released) { |tuple| tuple.name == plugin_query }
|
||||
matched_tuples = fetcher.detect(opts[:scope]) { |tuple| tuple.name == plugin_query }
|
||||
else
|
||||
regex = Regexp.new('^' + plugin_query + '.*')
|
||||
matched_tuples = fetcher.detect(:released) do |tuple|
|
||||
matched_tuples = fetcher.detect(opts[:scope]) do |tuple|
|
||||
tuple.name != 'inspec-core' && tuple.name =~ regex
|
||||
end
|
||||
end
|
||||
|
@ -179,8 +181,8 @@ module Inspec::Plugin::V2
|
|||
raise InstallError, "Could not find local gem file to install - #{opts[:gem_file]}"
|
||||
end
|
||||
elsif opts.key?(:path)
|
||||
unless Dir.exist?(opts[:path])
|
||||
raise InstallError, "Could not find directory for install from source path - #{opts[:path]}"
|
||||
unless File.exist?(opts[:path])
|
||||
raise InstallError, "Could not find path for install from source path - #{opts[:path]}"
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -226,10 +228,15 @@ module Inspec::Plugin::V2
|
|||
end
|
||||
end
|
||||
|
||||
def validate_search_opts(search_term, _opts)
|
||||
def validate_search_opts(search_term, opts)
|
||||
unless search_term =~ /^(inspec|train)-/
|
||||
raise SearchError, "All inspec plugins must begin with either 'inspec-' or 'train-'."
|
||||
end
|
||||
|
||||
opts[:scope] ||= :released
|
||||
unless [:prerelease, :released, :latest].include?(opts[:scope])
|
||||
raise SearchError, 'Search scope for listing versons must be :prerelease, :released, or :latest.'
|
||||
end
|
||||
end
|
||||
|
||||
#===================================================================#
|
||||
|
@ -276,6 +283,18 @@ module Inspec::Plugin::V2
|
|||
# OK, perform the installation.
|
||||
# Ignore deps here, because any needed deps should already be baked into new_plugin_dependency
|
||||
request_set.install_into(gem_path, true, ignore_dependencies: true)
|
||||
|
||||
# Painful aspect of rubygems: the VendorSet request set type needs to be able to find a gemspec
|
||||
# file within the source of the gem (and not all gems include it in their source tree; they are
|
||||
# not obliged to during packaging.)
|
||||
# So, after each install, run a scan for all gem(specs) we manage, and copy in their gemspec file
|
||||
# into the exploded gem source area if absent.
|
||||
loader.list_managed_gems.each do |spec|
|
||||
path_inside_source = File.join(spec.gem_dir, "#{spec.name}.gemspec")
|
||||
unless File.exist?(path_inside_source)
|
||||
File.write(path_inside_source, spec.to_ruby)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#===================================================================#
|
||||
|
@ -367,6 +386,7 @@ module Inspec::Plugin::V2
|
|||
# TODO: refactor the plugin.json file to have its own class, which Installer consumes
|
||||
def update_plugin_config_file(plugin_name, opts)
|
||||
config = update_plugin_config_data(plugin_name, opts)
|
||||
FileUtils.mkdir_p(Inspec.config_dir)
|
||||
File.write(plugin_conf_file_path, JSON.pretty_generate(config))
|
||||
end
|
||||
|
||||
|
|
|
@ -112,17 +112,25 @@ module Inspec::Plugin::V2
|
|||
|
||||
# Lists all gems found in the plugin_gem_path.
|
||||
# @return [Array[Gem::Specification]] Specs of all gems found.
|
||||
def list_managed_gems
|
||||
def self.list_managed_gems
|
||||
Dir.glob(File.join(plugin_gem_path, 'specifications', '*.gemspec')).map { |p| Gem::Specification.load(p) }
|
||||
end
|
||||
|
||||
def list_managed_gems
|
||||
self.class.list_managed_gems
|
||||
end
|
||||
|
||||
# Lists all plugin gems found in the plugin_gem_path.
|
||||
# This is simply all gems that begin with train- or inspec-.
|
||||
# @return [Array[Gem::Specification]] Specs of all gems found.
|
||||
def list_installed_plugin_gems
|
||||
def self.list_installed_plugin_gems
|
||||
list_managed_gems.select { |spec| spec.name.match(/^(inspec|train)-/) }
|
||||
end
|
||||
|
||||
def list_installed_plugin_gems
|
||||
self.class.list_managed_gems
|
||||
end
|
||||
|
||||
# TODO: refactor the plugin.json file to have its own class, which Loader consumes
|
||||
def plugin_conf_file_path
|
||||
self.class.plugin_conf_file_path
|
||||
|
@ -200,7 +208,7 @@ module Inspec::Plugin::V2
|
|||
File.join(bundle_dir, 'train-*.rb'),
|
||||
]
|
||||
Dir.glob(globs).each do |loader_file|
|
||||
name = File.basename(loader_file, '.rb').gsub(/^(inspec|train)-/, '')
|
||||
name = File.basename(loader_file, '.rb').to_sym
|
||||
status = Inspec::Plugin::V2::Status.new
|
||||
status.name = name
|
||||
status.entry_point = loader_file
|
||||
|
@ -216,9 +224,9 @@ module Inspec::Plugin::V2
|
|||
# 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.name = File.basename(plugin_dir).to_sym
|
||||
status.entry_point = File.join(plugin_dir, 'lib', status.name.to_s + '.rb')
|
||||
status.installation_type = :core
|
||||
status.loaded = false
|
||||
registry[status.name.to_sym] = status
|
||||
end
|
||||
|
|
|
@ -64,7 +64,7 @@ module Inspec::Plugin::V2
|
|||
|
||||
def register(name, status)
|
||||
if known_plugin? name
|
||||
Inspec::Log.warn "PluginLoader: refusing to re-register plugin '#{name}': an existing plugin with that name was loaded via #{existing.installation_type}-loading from #{existing.entry_point}"
|
||||
Inspec::Log.debug "PluginLoader: refusing to re-register plugin '#{name}': an existing plugin with that name was loaded via #{registry[name].installation_type}-loading from #{registry[name].entry_point}"
|
||||
else
|
||||
registry[name.to_sym] = status
|
||||
end
|
||||
|
|
6
lib/plugins/inspec-plugin-manager-cli/README.md
Normal file
6
lib/plugins/inspec-plugin-manager-cli/README.md
Normal file
|
@ -0,0 +1,6 @@
|
|||
# InSpec Plugin Manager CLI
|
||||
|
||||
This is a CLI plugin for InSpec. It uses the Plugins API v2 to create a
|
||||
series of commands to manage plugins.
|
||||
|
||||
It was the first plugin to be authored as a core plugin under Plugins v2.
|
|
@ -0,0 +1,18 @@
|
|||
|
||||
# Because this is a core plugin, we place the plugin definition here in the entry point.
|
||||
# This is needed because under core testing, the entry point may be reloaded multiple times,
|
||||
# and we need plugin registration to properly occur each time.
|
||||
# More typically, the entry point would just load a plugin definition file.
|
||||
|
||||
module InspecPlugins
|
||||
module PluginManager
|
||||
class Plugin < Inspec.plugin(2)
|
||||
plugin_name :'inspec-plugin-manager-cli'
|
||||
|
||||
cli_command :plugin do
|
||||
require_relative 'inspec-plugin-manager-cli/cli_command'
|
||||
InspecPlugins::PluginManager::CliCommand
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,394 @@
|
|||
require 'term/ansicolor'
|
||||
require 'pathname'
|
||||
require 'inspec/plugin/v2/installer'
|
||||
|
||||
module InspecPlugins
|
||||
module PluginManager
|
||||
class CliCommand < Inspec.plugin(2, :cli_command)
|
||||
include Term::ANSIColor
|
||||
|
||||
subcommand_desc 'plugin SUBCOMMAND', 'Manage InSpec plugins'
|
||||
|
||||
#==================================================================#
|
||||
# inspec plugin list
|
||||
#==================================================================#
|
||||
|
||||
desc 'list [options]', 'Lists user-installed InSpec plugins.'
|
||||
option :all, desc: 'Include plugins shipped with InSpec as well.', type: :boolean, aliases: [:a]
|
||||
def list
|
||||
plugin_statuses = Inspec::Plugin::V2::Registry.instance.plugin_statuses
|
||||
plugin_statuses.reject! { |s| [:core, :bundle].include?(s.installation_type) } unless options[:all]
|
||||
|
||||
# TODO: ui object support
|
||||
puts
|
||||
puts(bold { format(' %-30s%-10s%-8s%-6s', 'Plugin Name', 'Version', 'Via', 'ApiVer') })
|
||||
puts '-' * 55
|
||||
plugin_statuses.sort_by(&:name).each do |status|
|
||||
puts(format(' %-30s%-10s%-8s%-6s', status.name, make_pretty_version(status), status.installation_type, status.api_generation.to_s))
|
||||
end
|
||||
puts '-' * 55
|
||||
puts(" #{plugin_statuses.count} plugin(s) total")
|
||||
puts
|
||||
end
|
||||
|
||||
#==================================================================#
|
||||
# inspec plugin search
|
||||
#==================================================================#
|
||||
|
||||
desc 'search [options] PATTERN', 'Searches rubygems.org for InSpec plugins. Exits 0 on a search hit, exits 2 on a search miss.'
|
||||
option :all, desc: 'List all available versions, not just the latest one.', type: :boolean, aliases: [:a]
|
||||
option :exact, desc: 'Assume PATTERN is exact; do not add a wildcard to the end', type: :boolean, aliases: [:e]
|
||||
# Justification for disabling ABC: currently at 33.51/33
|
||||
def search(search_term) # rubocop: disable Metrics/AbcSize
|
||||
search_results = installer.search(search_term, exact: options[:exact])
|
||||
|
||||
# TODO: ui object support
|
||||
puts
|
||||
puts(bold { format(' %-30s%-50s%', 'Plugin Name', 'Versions Available') })
|
||||
puts '-' * 55
|
||||
search_results.keys.sort.each do |plugin_name|
|
||||
versions = options[:all] ? search_results[plugin_name] : [search_results[plugin_name].first]
|
||||
versions = '(' + versions.join(', ') + ')'
|
||||
puts(format(' %-30s%-50s', plugin_name, versions))
|
||||
end
|
||||
puts '-' * 55
|
||||
puts(" #{search_results.count} plugin(s) found")
|
||||
puts
|
||||
|
||||
exit 2 if search_results.empty?
|
||||
rescue Inspec::Plugin::V2::SearchError => ex
|
||||
Inspec::Log.error ex.message
|
||||
exit 1
|
||||
end
|
||||
|
||||
#==================================================================#
|
||||
# inspec plugin install
|
||||
#==================================================================#
|
||||
desc 'install [-v VERSION] PLUGIN', 'Installs a plugin from rubygems.org, a gemfile, or a path to local source.'
|
||||
long_desc <<~EOLD
|
||||
PLUGIN may be the name of a gem on rubygems.org that begins with inspec- or train-.
|
||||
PLUGIN may also be the path to a local gemfile, which will then be installed like
|
||||
any other gem. Finally, if PLUGIN is a path ending in .rb, it is taken to be a
|
||||
local file that will act as athe entry point for a plugin (this mode is provided
|
||||
for local plugin development). Exit codes are 0 on success, 2 if the plugin is
|
||||
already installed, and 1 if any other error occurs.
|
||||
EOLD
|
||||
option :version, desc: 'When installing from rubygems.org, specifies a specific version to install.', aliases: [:v]
|
||||
def install(plugin_id_arg)
|
||||
if plugin_id_arg =~ /\.gem$/ # Does it end in .gem?
|
||||
install_from_gemfile(plugin_id_arg)
|
||||
elsif plugin_id_arg =~ %r{[\/\\]} || Dir.exist?(plugin_id_arg) # Does the argument have a slash, or exist as dir in the local directory?
|
||||
install_from_path(plugin_id_arg)
|
||||
else
|
||||
install_from_remote_gem(plugin_id_arg)
|
||||
end
|
||||
end
|
||||
|
||||
#--------------------------
|
||||
# update
|
||||
#--------------------------
|
||||
desc 'update PLUGIN', 'Updates a plugin to the latest from from rubygems.org'
|
||||
long_desc <<~EOLD
|
||||
PLUGIN may be the name of a gem on rubygems.org that begins with inspec- or train-.
|
||||
Exit codes are 0 on success, 2 if the plugin is already up to date, and 1 if any
|
||||
other error occurs.
|
||||
EOLD
|
||||
def update(plugin_name)
|
||||
pre_update_versions = installer.list_installed_plugin_gems.select { |spec| spec.name == plugin_name }.map { |spec| spec.version.to_s }
|
||||
old_version = pre_update_versions.join(', ')
|
||||
|
||||
update_preflight_check(plugin_name, pre_update_versions)
|
||||
|
||||
begin
|
||||
installer.update(plugin_name)
|
||||
rescue Inspec::Plugin::V2::UpdateError => ex
|
||||
puts(red { 'Update error: ' } + ex.message + ' - update failed')
|
||||
exit 1
|
||||
end
|
||||
post_update_versions = installer.list_installed_plugin_gems.select { |spec| spec.name == plugin_name }.map { |spec| spec.version.to_s }
|
||||
new_version = (post_update_versions - pre_update_versions).first
|
||||
|
||||
puts(bold { plugin_name } + " plugin, version #{old_version} -> #{new_version}, updated from rubygems.org")
|
||||
end
|
||||
|
||||
#--------------------------
|
||||
# uninstall
|
||||
#--------------------------
|
||||
desc 'uninstall PLUGIN_NAME', 'Uninstalls a gem- or path- based plugin'
|
||||
long_desc <<~EOLD
|
||||
Removes a plugin from the users configuration.
|
||||
In the case of a gem plugin (by far the most common), the plugin gem is removed, along
|
||||
with any of its dependencies that are no longer needed by anything else. Finally, the
|
||||
plugin configuration file is updated to reflect that the plugin is no longer present.
|
||||
In the case of a path-based plugin (often used for plugin development), no changes
|
||||
are made to the referenced plugin source code. Rather, the plugin's entry is simply removed
|
||||
from the plugin config file.
|
||||
EOLD
|
||||
def uninstall(plugin_name)
|
||||
status = Inspec::Plugin::V2::Registry.instance[plugin_name.to_sym]
|
||||
unless status
|
||||
puts(red { 'No such plugin installed: ' } + "#{plugin_name} is not installed - uninstall failed")
|
||||
|
||||
exit 1
|
||||
end
|
||||
installer = Inspec::Plugin::V2::Installer.instance
|
||||
|
||||
pre_uninstall_versions = installer.list_installed_plugin_gems.select { |spec| spec.name == plugin_name }.map { |spec| spec.version.to_s }
|
||||
old_version = pre_uninstall_versions.join(', ')
|
||||
|
||||
installer.uninstall(plugin_name)
|
||||
|
||||
if status.installation_type == :path
|
||||
puts(bold { plugin_name } + ' path-based plugin install has been uninstalled')
|
||||
else
|
||||
puts(bold { plugin_name } + " plugin, version #{old_version}, has been uninstalled")
|
||||
end
|
||||
exit 0
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
#==================================================================#
|
||||
# install breakdown
|
||||
#==================================================================#
|
||||
# These are broken down because rubocop complained.
|
||||
|
||||
def install_from_gemfile(gem_file)
|
||||
unless File.exist? gem_file
|
||||
puts(red { 'No such plugin gem file ' } + gem_file + ' - installation failed.')
|
||||
exit 1
|
||||
end
|
||||
|
||||
plugin_name_parts = File.basename(gem_file, '.gem').split('-')
|
||||
version = plugin_name_parts.pop
|
||||
plugin_name = plugin_name_parts.join('-')
|
||||
check_plugin_name(plugin_name, 'installation')
|
||||
|
||||
installer.install(plugin_name, gem_file: gem_file)
|
||||
|
||||
puts(bold { plugin_name } + " plugin, version #{version}, installed from local .gem file")
|
||||
exit 0
|
||||
end
|
||||
|
||||
def install_from_path(path)
|
||||
unless File.exist? path
|
||||
puts(red { 'No such source code path ' } + path + ' - installation failed.')
|
||||
exit 1
|
||||
end
|
||||
|
||||
plugin_name = File.basename(path, '.rb')
|
||||
|
||||
# While installer.install does some rudimentary checking,
|
||||
# this file has good UI access, so we promise to validate the
|
||||
# input a lot and hand the installer a sure-thing.
|
||||
|
||||
# Name OK?
|
||||
check_plugin_name(plugin_name, 'installation')
|
||||
|
||||
# Already installed?
|
||||
if registry.known_plugin?(plugin_name.to_sym)
|
||||
puts(red { 'Plugin already installed' } + " - #{plugin_name} - Use 'inspec plugin list' to see previously installed plugin - installation failed.")
|
||||
exit 2
|
||||
end
|
||||
|
||||
# Can we figure out how to load it?
|
||||
entry_point = install_from_path__apply_entry_point_heuristics(path)
|
||||
|
||||
# If you load it, does it act like a plugin?
|
||||
install_from_path__probe_load(entry_point, plugin_name)
|
||||
|
||||
# OK, install it!
|
||||
installer.install(plugin_name, path: entry_point)
|
||||
|
||||
puts(bold { plugin_name } + ' plugin installed via source path reference, resolved to entry point ' + entry_point)
|
||||
exit 0
|
||||
end
|
||||
|
||||
# Rationale for rubocop variances: It's a heuristics method, and will be full of
|
||||
# conditionals. The code is well-commented; refactoring into sub-methods would
|
||||
# reduce clarity.
|
||||
def install_from_path__apply_entry_point_heuristics(path) # rubocop: disable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
||||
given = Pathname.new(path)
|
||||
given = given.expand_path # Resolve any relative paths
|
||||
name_regex = /^(inspec|train)-/
|
||||
|
||||
# What are the last four things like?
|
||||
parts = [
|
||||
given.parent.parent.basename,
|
||||
given.parent.basename,
|
||||
given.basename('.rb'),
|
||||
given.extname,
|
||||
].map(&:to_s)
|
||||
|
||||
# Simplest case: it was a full entry point, as presented.
|
||||
# /home/you/projects/inspec-something/lib/inspec-something.rb
|
||||
# parts index: ^0^ ^1^ ^2^ ^3^
|
||||
if parts[0] =~ name_regex && parts[1] == 'lib' && parts[2] == parts[0] && parts[3] == '.rb'
|
||||
return given.to_s
|
||||
end
|
||||
|
||||
# Also easy: they either referred to the internal library directory,
|
||||
# or left the extansion off. Those are the same to us.
|
||||
# /home/you/projects/inspec-something/lib/inspec-something
|
||||
# parts index: ^0^ ^1^ ^2^ (3 is empty)
|
||||
if parts[0] =~ name_regex && parts[1] == 'lib' && parts[2] == parts[0] && parts[3].empty?
|
||||
return given.to_s + '.rb'
|
||||
end
|
||||
|
||||
# Easy to recognize, but harder to handle: they referred to the project root.
|
||||
# /home/you/projects/inspec-something
|
||||
# parts index: ^0^ ^1^ ^2^ (3 is empty)
|
||||
# 0 and 1 are not meaningful to us, but we hope to find a parts[2]/lib/inspec-something.rb.
|
||||
entry_point_guess = File.join(given.to_s, 'lib', parts[2] + '.rb')
|
||||
if parts[2] =~ name_regex && File.exist?(entry_point_guess)
|
||||
return entry_point_guess
|
||||
end
|
||||
|
||||
# Well, if we got here, parts[2] matches an inspec/train prefix, but we have no idea about anything.
|
||||
# Give up.
|
||||
puts(red { 'Unrecognizable plugin structure' } + " - #{parts[2]} - When installing from a path, please provide the path of the entry point file - installation failed.")
|
||||
exit 1
|
||||
end
|
||||
|
||||
def install_from_path__probe_load(entry_point, plugin_name)
|
||||
# Brazenly attempt to load a file, and see if it registers a plugin.
|
||||
begin
|
||||
require entry_point
|
||||
rescue LoadError => ex
|
||||
puts(red { 'Plugin contains errors' } + " - #{plugin_name} - Encountered errors while trying to test load the plugin entry point, resolved to #{entry_point} - installation failed")
|
||||
puts ex.message
|
||||
exit 1
|
||||
end
|
||||
|
||||
# OK, the wheels didn't fall off. But is it a plugin?
|
||||
unless Inspec::Plugin::V2::Registry.instance.known_plugin?(plugin_name.to_sym)
|
||||
puts(red { 'Does not appear to be a plugin' } + " - #{plugin_name} - After probe-loading the supposed plugin, it did not register itself. Ensure something inherits from 'Inspec.plugin(2)' - installation failed.")
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
def install_from_remote_gem(plugin_name)
|
||||
requested_version = options[:version]
|
||||
|
||||
check_plugin_name(plugin_name, 'installation')
|
||||
|
||||
# Version pre-flighting
|
||||
pre_installed_versions = installer.list_installed_plugin_gems.select { |spec| spec.name == plugin_name }.map { |spec| spec.version.to_s }
|
||||
install_from_remote_gem_verson_preflight_check(plugin_name, requested_version, pre_installed_versions)
|
||||
|
||||
install_attempt_install(plugin_name)
|
||||
|
||||
# Success messaging. What did we actually install?
|
||||
post_installed_versions = installer.list_installed_plugin_gems.select { |spec| spec.name == plugin_name }.map { |spec| spec.version.to_s }
|
||||
new_version = (post_installed_versions - pre_installed_versions).first
|
||||
|
||||
puts(bold { plugin_name } + " plugin, version #{new_version}, installed from rubygems.org")
|
||||
exit 0
|
||||
end
|
||||
|
||||
def install_from_remote_gem_verson_preflight_check(plugin_name, requested_version, pre_installed_versions)
|
||||
return if pre_installed_versions.empty?
|
||||
|
||||
# Everything past here in the block is a code 2 error
|
||||
|
||||
# If they didn't ask for a specific version, they implicitly ask for the latest.
|
||||
# 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
|
||||
end
|
||||
end
|
||||
|
||||
# Check for already-installed at desired version conditions
|
||||
they_explicitly_asked_for_a_version = !options[:version].nil?
|
||||
what_we_would_install_is_already_installed = pre_installed_versions.include?(requested_version)
|
||||
if what_we_would_install_is_already_installed && they_explicitly_asked_for_a_version
|
||||
puts(red { 'Plugin already installed at requested version' } + " - plugin #{plugin_name} #{requested_version} - refusing to install.")
|
||||
elsif what_we_would_install_is_already_installed && !they_explicitly_asked_for_a_version
|
||||
puts(red { 'Plugin already installed at latest version' } + " - plugin #{plugin_name} #{requested_version} - refusing to install.")
|
||||
else
|
||||
# There are existing versions installed, but none of them are what was requested
|
||||
puts(red { 'Update required' } + " - plugin #{plugin_name}, requested #{requested_version}, have #{pre_installed_versions.join(', ')}; use `inspec plugin update` - refusing to install.")
|
||||
end
|
||||
|
||||
exit 2
|
||||
end
|
||||
|
||||
def install_attempt_install(plugin_name)
|
||||
installer.install(plugin_name, version: options[:version])
|
||||
rescue Inspec::Plugin::V2::InstallError
|
||||
results = installer.search(plugin_name, exact: true)
|
||||
if results.empty?
|
||||
puts(red { 'No such plugin gem ' } + plugin_name + ' could be found on rubygems.org - installation failed.')
|
||||
elsif options[:version] && !results[plugin_name].include?(options[:version])
|
||||
puts(red { 'No such version' } + ' - ' + plugin_name + " exists, but no such version #{options[:version]} found on rubygems.org - installation failed.")
|
||||
else
|
||||
puts(red { 'Unknown error occured ' } + ' - installation failed.')
|
||||
end
|
||||
exit 1
|
||||
end
|
||||
|
||||
#==================================================================#
|
||||
# update breakdown
|
||||
#==================================================================#
|
||||
def update_preflight_check(plugin_name, pre_update_versions)
|
||||
if pre_update_versions.empty?
|
||||
# Check for path install
|
||||
status = Inspec::Plugin::V2::Registry.instance[plugin_name.to_sym]
|
||||
if !status
|
||||
puts(red { 'No such plugin installed: ' } + "#{plugin_name} - update failed")
|
||||
exit 1
|
||||
elsif status.installation_type == :path
|
||||
puts(red { 'Cannot update path-based install: ' } + "#{plugin_name} is installed via path reference; use `inspec plugin uninstall` to remove - refusing to update")
|
||||
exit 2
|
||||
end
|
||||
end
|
||||
|
||||
# Check for latest version (and implicitly, existance)
|
||||
latest_version = installer.search(plugin_name, exact: true, scope: :latest)
|
||||
latest_version = latest_version[plugin_name]&.last
|
||||
|
||||
if pre_update_versions.include?(latest_version)
|
||||
puts(red { 'Already installed at latest version: ' } + "#{plugin_name} is at #{latest_version}, which the latest - refusing to update")
|
||||
exit 2
|
||||
end
|
||||
end
|
||||
|
||||
#==================================================================#
|
||||
# utilities
|
||||
#==================================================================#
|
||||
def installer
|
||||
Inspec::Plugin::V2::Installer.instance
|
||||
end
|
||||
|
||||
def registry
|
||||
Inspec::Plugin::V2::Registry.instance
|
||||
end
|
||||
|
||||
def check_plugin_name(plugin_name, action)
|
||||
unless plugin_name =~ /^(inspec|train)-/
|
||||
puts(red { 'Invalid plugin name' } + " - #{plugin_name} - All inspec plugins must begin with either 'inspec-' or 'train-' - #{action} failed.")
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
def make_pretty_version(status)
|
||||
case status.installation_type
|
||||
when :core, :bundle
|
||||
Inspec::VERSION
|
||||
when :gem
|
||||
# TODO: this is naive, and assumes the latest version is the one that will be used. Logged on #3317
|
||||
# In fact, the logic to determine "what version would be used" belongs in the Loader.
|
||||
Inspec::Plugin::V2::Loader.list_installed_plugin_gems
|
||||
.select { |spec| spec.name == status.name.to_s }
|
||||
.sort_by(&:version)
|
||||
.last.version
|
||||
when :path
|
||||
'src'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,12 @@
|
|||
module InspecPlugins
|
||||
module PluginManager
|
||||
class Plugin < Inspec.plugin(2)
|
||||
plugin_name :'inspec-plugin-manager-cli'
|
||||
|
||||
cli_command :plugin do
|
||||
require_relative 'cli'
|
||||
InspecPlugins::PluginManager::CliCommand
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
0
lib/plugins/inspec-plugin-manager-cli/test/fixtures/config_dirs/empty/.gitkeep
vendored
Normal file
0
lib/plugins/inspec-plugin-manager-cli/test/fixtures/config_dirs/empty/.gitkeep
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
# Despite having the right file structure, and the right name,
|
||||
# this is not actually an InSpec plugin.
|
0
lib/plugins/inspec-plugin-manager-cli/test/fixtures/plugins/inspec-wrong-structure/.gitkeep
vendored
Normal file
0
lib/plugins/inspec-plugin-manager-cli/test/fixtures/plugins/inspec-wrong-structure/.gitkeep
vendored
Normal file
1
lib/plugins/inspec-plugin-manager-cli/test/fixtures/plugins/wrong-name/lib/wrong-name.rb
vendored
Normal file
1
lib/plugins/inspec-plugin-manager-cli/test/fixtures/plugins/wrong-name/lib/wrong-name.rb
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
# This should should never even be loaded.
|
|
@ -0,0 +1,577 @@
|
|||
#=========================================================================================#
|
||||
# `inspec plugin SUBCOMMAND` facility
|
||||
#=========================================================================================#
|
||||
require_relative '../../../shared/core_plugin_test_helper.rb'
|
||||
|
||||
#-----------------------------------------------------------------------------------------#
|
||||
# utilities
|
||||
#-----------------------------------------------------------------------------------------#
|
||||
module PluginManagerHelpers
|
||||
let(:project_repo_path) { File.expand_path(File.join(__FILE__, '..', '..', '..')) }
|
||||
let(:project_fixtures_path) { File.join(project_repo_path, 'test', 'fixtures') }
|
||||
let(:project_config_dirs_path) { File.join(project_fixtures_path, 'config_dirs') }
|
||||
let(:empty_config_dir_path) { File.join(project_config_dirs_path, 'empty') }
|
||||
|
||||
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.
|
||||
run_result.payload.list_result = run_inspec_process('plugin list', env: { INSPEC_CONFIG_DIR: tmp_dir })
|
||||
end
|
||||
end
|
||||
|
||||
def copy_in_project_config_dir(fixture_name, dest = nil)
|
||||
src = Dir.glob(File.join(project_config_dirs_path, fixture_name, '*'))
|
||||
dest ||= File.join(project_config_dirs_path, 'empty')
|
||||
src.each { |path| FileUtils.cp_r(path, dest) }
|
||||
end
|
||||
|
||||
def copy_in_core_config_dir(fixture_name, dest = nil)
|
||||
src = Dir.glob(File.join(core_config_dir_path, fixture_name, '*'))
|
||||
dest ||= File.join(project_config_dirs_path, 'empty')
|
||||
src.each { |path| FileUtils.cp_r(path, dest) }
|
||||
end
|
||||
|
||||
def clear_empty_config_dir
|
||||
Dir.glob(File.join(project_config_dirs_path, 'empty', '*')).each do |path|
|
||||
next if path.end_with? '.gitkeep'
|
||||
FileUtils.rm_rf(path)
|
||||
end
|
||||
end
|
||||
|
||||
def teardown
|
||||
clear_empty_config_dir
|
||||
end
|
||||
end
|
||||
|
||||
#-----------------------------------------------------------------------------------------#
|
||||
# inspec help
|
||||
#-----------------------------------------------------------------------------------------#
|
||||
class PluginManagerCliHelp < MiniTest::Test
|
||||
include CorePluginFunctionalHelper
|
||||
|
||||
# Main inspec help subcommand listing
|
||||
def test_inspec_help_includes_plugin
|
||||
result = run_inspec_process_with_this_plugin('help')
|
||||
assert_includes result.stdout, 'inspec plugin'
|
||||
end
|
||||
|
||||
# inspec plugin help subcommand listing
|
||||
def test_inspec_plugin_help_includes_plugin
|
||||
result = run_inspec_process_with_this_plugin('plugin help')
|
||||
assert_includes result.stdout, 'inspec plugin list'
|
||||
assert_includes result.stdout, 'inspec plugin search'
|
||||
assert_includes result.stdout, 'inspec plugin install'
|
||||
assert_includes result.stdout, 'inspec plugin update'
|
||||
assert_includes result.stdout, 'inspec plugin uninstall'
|
||||
end
|
||||
end
|
||||
|
||||
#-----------------------------------------------------------------------------------------#
|
||||
# inspec plugin list
|
||||
#-----------------------------------------------------------------------------------------#
|
||||
class PluginManagerCliList < MiniTest::Test
|
||||
include CorePluginFunctionalHelper
|
||||
include PluginManagerHelpers
|
||||
|
||||
def test_list_when_no_user_plugins_installed
|
||||
result = run_inspec_process_with_this_plugin('plugin list')
|
||||
assert_equal 0, result.exit_status, 'exist status must be 0'
|
||||
assert_includes result.stdout, '0 plugin(s) total', 'Empty list should include zero count'
|
||||
end
|
||||
|
||||
def test_list_all_when_no_user_plugins_installed
|
||||
result = run_inspec_process_with_this_plugin('plugin list --all')
|
||||
assert_equal 0, result.exit_status, 'exist status must be 0'
|
||||
assert_includes result.stdout, '6 plugin(s) total', '--all list should find six'
|
||||
assert_includes result.stdout, 'inspec-plugin-manager-cli', '--all list should find inspec-plugin-manager-cli'
|
||||
assert_includes result.stdout, 'habitat', '--all list should find habitat'
|
||||
|
||||
result = run_inspec_process_with_this_plugin('plugin list -a')
|
||||
assert_equal 0, result.exit_status, 'exist status must be 0'
|
||||
assert_includes result.stdout, '6 plugin(s) total', '-a list should find six'
|
||||
end
|
||||
|
||||
def test_list_when_gem_and_path_plugins_installed
|
||||
pre_block = Proc.new do |plugin_statefile_data, tmp_dir|
|
||||
plugin_statefile_data.clear # Signal not to write a file, we'll provide one.
|
||||
copy_in_core_config_dir('test-fixture-1-float', tmp_dir)
|
||||
end
|
||||
|
||||
result = run_inspec_process_with_this_plugin('plugin list', pre_run: pre_block)
|
||||
assert_equal 0, result.exit_status, 'exist status must be 0'
|
||||
assert_includes result.stdout, '2 plugin(s) total', 'gem+path should show two plugins'
|
||||
|
||||
# Plugin Name Version Via ApiVer
|
||||
# -------------------------------------------------------
|
||||
# inspec-meaning-of-life src path 2
|
||||
# inspec-test-fixture 0.1.0 gem 2
|
||||
# -------------------------------------------------------
|
||||
# 2 plugin(s) total
|
||||
gem_line = result.stdout.split("\n").grep(/gem/).first
|
||||
assert_match(/\s*inspec-\S+\s+\d+\.\d+\.\d+\s+gem\s+2/, gem_line)
|
||||
path_line = result.stdout.split("\n").grep(/path/).first
|
||||
assert_match(/\s*inspec-\S+\s+src\s+path\s+2/, path_line)
|
||||
end
|
||||
end
|
||||
|
||||
#-----------------------------------------------------------------------------------------#
|
||||
# inspec plugin search
|
||||
#-----------------------------------------------------------------------------------------#
|
||||
class PluginManagerCliSearch < MiniTest::Test
|
||||
include CorePluginFunctionalHelper
|
||||
include PluginManagerHelpers
|
||||
|
||||
def test_search_for_a_real_gem_with_full_name_no_options
|
||||
result = run_inspec_process('plugin search inspec-test-fixture')
|
||||
assert_equal 0, result.exit_status, 'Search should exit 0 on a hit'
|
||||
assert_includes result.stdout, 'inspec-test-fixture', 'Search result should contain the gem name'
|
||||
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')
|
||||
end
|
||||
|
||||
def test_search_for_a_real_gem_with_stub_name_no_options
|
||||
result = run_inspec_process('plugin search inspec-test-')
|
||||
assert_equal 0, result.exit_status, 'Search should exit 0 on a hit'
|
||||
assert_includes result.stdout, 'inspec-test-fixture', 'Search result should contain the gem name'
|
||||
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')
|
||||
end
|
||||
|
||||
def test_search_for_a_real_gem_with_full_name_and_exact_option
|
||||
result = run_inspec_process('plugin search --exact inspec-test-fixture')
|
||||
assert_equal 0, result.exit_status, 'Search should exit 0 on a hit'
|
||||
assert_includes result.stdout, 'inspec-test-fixture', 'Search result should contain the gem name'
|
||||
assert_includes result.stdout, '1 plugin(s) found', 'Search result should find 1 plugin'
|
||||
|
||||
result = run_inspec_process('plugin search -e inspec-test-fixture')
|
||||
assert_equal 0, result.exit_status, 'Search should exit 0 on a hit'
|
||||
end
|
||||
|
||||
def test_search_for_a_real_gem_with_stub_name_and_exact_option
|
||||
result = run_inspec_process('plugin search --exact inspec-test-')
|
||||
assert_equal 2, result.exit_status, 'Search should exit 2 on a miss'
|
||||
assert_includes result.stdout, '0 plugin(s) found', 'Search result should find 0 plugins'
|
||||
|
||||
result = run_inspec_process('plugin search -e inspec-test-')
|
||||
assert_equal 2, result.exit_status, 'Search should exit 2 on a miss'
|
||||
end
|
||||
|
||||
def test_search_for_a_real_gem_with_full_name_and_all_option
|
||||
result = run_inspec_process('plugin search --all inspec-test-fixture')
|
||||
assert_equal 0, result.exit_status, 'Search should exit 0 on a hit'
|
||||
assert_includes result.stdout, 'inspec-test-fixture', 'Search result should contain the gem name'
|
||||
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+(,\s)?){2,}\)/,line,'Plugin line should include name and at least two versions')
|
||||
|
||||
result = run_inspec_process('plugin search -a inspec-test-fixture')
|
||||
assert_equal 0, result.exit_status, 'Search should exit 0 on a hit'
|
||||
end
|
||||
|
||||
def test_search_for_a_gem_with_missing_prefix
|
||||
result = run_inspec_process('plugin search test-fixture')
|
||||
assert_equal 1, result.exit_status, 'Search should exit 1 on user error'
|
||||
assert_includes result.stdout, "All inspec plugins must begin with either 'inspec-' or 'train-'"
|
||||
end
|
||||
|
||||
def test_search_for_a_gem_that_does_not_exist
|
||||
result = run_inspec_process('plugin search inspec-test-fixture-nonesuch')
|
||||
assert_equal 2, result.exit_status, 'Search should exit 2 on a miss'
|
||||
assert_includes result.stdout, '0 plugin(s) found', 'Search result should find 0 plugins'
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
#-----------------------------------------------------------------------------------------#
|
||||
# inspec plugin install
|
||||
#-----------------------------------------------------------------------------------------#
|
||||
class PluginManagerCliInstall < MiniTest::Test
|
||||
include CorePluginFunctionalHelper # gives us instance methods, like `let` aliases inside test methods
|
||||
extend CorePluginFunctionalHelper # gives us class methods, like `let` aliases out here outside test methods
|
||||
|
||||
include PluginManagerHelpers
|
||||
|
||||
# Test multiple hueristics of the path-mode install.
|
||||
# These are all positive tests; they should resolve the entry point to the same path in each case.
|
||||
{
|
||||
'is_perfect' => File.join(core_fixture_plugins_path, 'inspec-test-fixture', 'lib', 'inspec-test-fixture.rb'),
|
||||
'refers_to_the_entry_point_with_no_extension' => File.join(core_fixture_plugins_path, 'inspec-test-fixture', 'lib', 'inspec-test-fixture'),
|
||||
'refers_to_the_src_root_of_a_plugin' => File.join(core_fixture_plugins_path, 'inspec-test-fixture'),
|
||||
'refers_to_a_relative_path' => File.join('test', 'unit', 'mock', 'plugins', 'inspec-test-fixture', 'lib', 'inspec-test-fixture.rb'),
|
||||
}.each do |test_name, fixture_plugin_path|
|
||||
define_method(('test_install_from_path_when_path_' + test_name).to_sym) do
|
||||
install_result = run_inspec_process_with_this_plugin("plugin install #{fixture_plugin_path}", post_run: list_after_run)
|
||||
|
||||
assert_empty install_result.stderr
|
||||
assert_equal 0, install_result.exit_status, 'Exit status should be 0'
|
||||
|
||||
# Check UX messaging
|
||||
success_message = install_result.stdout.split("\n").grep(/installed/).last
|
||||
refute_nil success_message, 'Should find a success message at the end'
|
||||
assert_includes success_message, 'inspec-test-fixture'
|
||||
assert_includes success_message, 'plugin installed via source path reference'
|
||||
|
||||
# Check round-trip UX via list
|
||||
list_result = install_result.payload.list_result
|
||||
itf_line = list_result.stdout.split("\n").grep(/inspec-test-fixture/).first
|
||||
refute_nil itf_line, 'inspec-test-fixture should now appear in the output of inspec list'
|
||||
assert_match(/\s*inspec-test-fixture\s+src\s+path\s+/, itf_line, 'list output should show that it is a path installation')
|
||||
|
||||
# Check plugin statefile. Extra important in this case, since all should resolve to the same entry point.
|
||||
plugin_data = install_result.payload.plugin_data
|
||||
entry = plugin_data['plugins'].detect { |e| e['name'] == 'inspec-test-fixture' }
|
||||
expected = File.join(core_fixture_plugins_path, 'inspec-test-fixture', 'lib', 'inspec-test-fixture.rb')
|
||||
assert_equal expected, entry['installation_path'], 'Regardless of input, the entry point should be correct.'
|
||||
end
|
||||
end
|
||||
|
||||
def test_fail_install_from_nonexistant_path
|
||||
bad_path = File.join(project_fixtures_path, 'none', 'such', 'inspec-test-fixture-nonesuch.rb')
|
||||
install_result = run_inspec_process_with_this_plugin("plugin install #{bad_path}")
|
||||
|
||||
assert_empty install_result.stderr
|
||||
assert_equal 1, install_result.exit_status, 'Exit status should be 1'
|
||||
|
||||
error_message = install_result.stdout.split("\n").last
|
||||
assert_includes error_message, "No such source code path"
|
||||
assert_includes error_message, 'inspec-test-fixture-nonesuch.rb'
|
||||
assert_includes error_message, 'installation failed'
|
||||
end
|
||||
|
||||
def test_fail_install_from_path_with_wrong_name
|
||||
bad_path = File.join(project_fixtures_path, 'plugins', 'wrong-name', 'lib', 'wrong-name.rb')
|
||||
install_result = run_inspec_process_with_this_plugin("plugin install #{bad_path}")
|
||||
|
||||
assert_empty install_result.stderr
|
||||
assert_equal 1, install_result.exit_status, 'Exit status should be 1'
|
||||
|
||||
error_message = install_result.stdout.split("\n").last
|
||||
assert_includes error_message, "Invalid plugin name"
|
||||
assert_includes error_message, 'wrong-name'
|
||||
assert_includes error_message, "All inspec plugins must begin with either 'inspec-' or 'train-'"
|
||||
assert_includes error_message, 'installation failed'
|
||||
end
|
||||
|
||||
def test_fail_install_from_path_when_it_is_not_a_plugin
|
||||
bad_path = File.join(project_fixtures_path, 'plugins', 'inspec-egg-white-omelette', 'lib', 'inspec-egg-white-omelette.rb')
|
||||
install_result = run_inspec_process_with_this_plugin("plugin install #{bad_path}")
|
||||
|
||||
assert_empty install_result.stderr
|
||||
assert_equal 1, install_result.exit_status, 'Exit status should be 1'
|
||||
|
||||
error_message = install_result.stdout.split("\n").last
|
||||
assert_includes error_message, "Does not appear to be a plugin"
|
||||
assert_includes error_message, 'inspec-egg-white-omelette'
|
||||
assert_includes error_message, "After probe-loading the supposed plugin, it did not register"
|
||||
assert_includes error_message, "Ensure something inherits from 'Inspec.plugin(2)'"
|
||||
assert_includes error_message, 'installation failed'
|
||||
end
|
||||
|
||||
def test_fail_install_from_path_when_it_is_already_installed
|
||||
plugin_path = File.join(core_fixture_plugins_path, 'inspec-test-fixture', 'lib', 'inspec-test-fixture.rb')
|
||||
pre_block = Proc.new do |plugin_data, _tmp_dir|
|
||||
plugin_data["plugins"] << {
|
||||
"name" => "inspec-test-fixture",
|
||||
"installation_type" => "path",
|
||||
"installation_path" => plugin_path,
|
||||
}
|
||||
end
|
||||
|
||||
install_result = run_inspec_process_with_this_plugin("plugin install #{plugin_path}", pre_run: pre_block)
|
||||
assert_empty install_result.stderr
|
||||
assert_equal 2, install_result.exit_status, 'Exit status on second install should be 2'
|
||||
|
||||
error_message = install_result.stdout.split("\n").last
|
||||
assert_includes error_message, "Plugin already installed"
|
||||
assert_includes error_message, 'inspec-test-fixture'
|
||||
assert_includes error_message, "Use 'inspec plugin list' to see previously installed plugin"
|
||||
assert_includes error_message, 'installation failed'
|
||||
end
|
||||
|
||||
def test_fail_install_from_path_when_the_dir_structure_is_wrong
|
||||
bad_path = File.join(project_fixtures_path, 'plugins', 'inspec-wrong-structure')
|
||||
install_result = run_inspec_process_with_this_plugin("plugin install #{bad_path}")
|
||||
|
||||
assert_empty install_result.stderr
|
||||
assert_equal 1, install_result.exit_status, 'Exit status should be 1'
|
||||
|
||||
error_message = install_result.stdout.split("\n").last
|
||||
assert_includes error_message, "Unrecognizable plugin structure"
|
||||
assert_includes error_message, 'inspec-wrong-structure'
|
||||
assert_includes error_message, ' When installing from a path, please provide the path of the entry point file'
|
||||
assert_includes error_message, 'installation failed'
|
||||
end
|
||||
|
||||
def test_install_from_gemfile
|
||||
fixture_gemfile_path = File.join(core_fixture_plugins_path, 'inspec-test-fixture', 'pkg', 'inspec-test-fixture-0.1.0.gem')
|
||||
install_result = run_inspec_process_with_this_plugin("plugin install #{fixture_gemfile_path}", post_run: list_after_run)
|
||||
|
||||
assert_empty install_result.stderr
|
||||
assert_equal 0, install_result.exit_status, 'Exit status should be 0'
|
||||
|
||||
success_message = install_result.stdout.split("\n").grep(/installed/).last
|
||||
refute_nil success_message, 'Should find a success message at the end'
|
||||
assert_includes success_message, 'inspec-test-fixture'
|
||||
assert_includes success_message, '0.1.0'
|
||||
assert_includes success_message, 'installed from local .gem file'
|
||||
|
||||
list_result = install_result.payload.list_result
|
||||
itf_line = list_result.stdout.split("\n").grep(/inspec-test-fixture/).first
|
||||
refute_nil itf_line, 'inspec-test-fixture should now appear in the output of inspec list'
|
||||
assert_match(/\s*inspec-test-fixture\s+0.1.0\s+gem\s+/, itf_line, 'list output should show that it is a gem installation with version')
|
||||
end
|
||||
|
||||
def test_fail_install_from_nonexistant_gemfile
|
||||
bad_path = File.join(project_fixtures_path, 'none', 'such', 'inspec-test-fixture-nonesuch-0.3.0.gem')
|
||||
install_result = run_inspec_process_with_this_plugin("plugin install #{bad_path}")
|
||||
|
||||
assert_empty install_result.stderr
|
||||
assert_equal 1, install_result.exit_status, 'Exit status should be 1'
|
||||
assert_match(/No such plugin gem file .+ - installation failed./, install_result.stdout)
|
||||
end
|
||||
|
||||
def test_install_from_rubygems_latest
|
||||
install_result = run_inspec_process_with_this_plugin('plugin install inspec-test-fixture', post_run: list_after_run)
|
||||
|
||||
assert_empty install_result.stderr
|
||||
assert_equal 0, install_result.exit_status, 'Exit status should be 0'
|
||||
|
||||
success_message = install_result.stdout.split("\n").grep(/installed/).last
|
||||
refute_nil success_message, 'Should find a success message at the end'
|
||||
assert_includes success_message, 'inspec-test-fixture'
|
||||
assert_includes success_message, '0.2.0'
|
||||
assert_includes success_message, 'installed from rubygems.org'
|
||||
|
||||
list_result = install_result.payload.list_result
|
||||
itf_line = list_result.stdout.split("\n").grep(/inspec-test-fixture/).first
|
||||
refute_nil itf_line, 'inspec-test-fixture should now appear in the output of inspec list'
|
||||
assert_match(/\s*inspec-test-fixture\s+0.2.0\s+gem\s+/, itf_line, 'list output should show that it is a gem installation with version')
|
||||
end
|
||||
|
||||
def test_fail_install_from_nonexistant_remote_rubygem
|
||||
install_result = run_inspec_process_with_this_plugin('plugin install inspec-test-fixture-nonesuch')
|
||||
|
||||
assert_empty install_result.stderr
|
||||
assert_equal 1, install_result.exit_status, 'Exit status should be 1'
|
||||
assert_match(/No such plugin gem .+ could be found on rubygems.org - installation failed./, install_result.stdout)
|
||||
end
|
||||
|
||||
def test_install_from_rubygems_with_pinned_version
|
||||
install_result = run_inspec_process_with_this_plugin('plugin install inspec-test-fixture -v 0.1.0', post_run: list_after_run)
|
||||
|
||||
assert_empty install_result.stderr
|
||||
assert_equal 0, install_result.exit_status, 'Exit status should be 0'
|
||||
|
||||
success_message = install_result.stdout.split("\n").grep(/installed/).last
|
||||
refute_nil success_message, 'Should find a success message at the end'
|
||||
assert_includes success_message, 'inspec-test-fixture'
|
||||
assert_includes success_message, '0.1.0'
|
||||
assert_includes success_message, 'installed from rubygems.org'
|
||||
|
||||
list_result = install_result.payload.list_result
|
||||
itf_line = list_result.stdout.split("\n").grep(/inspec-test-fixture/).first
|
||||
refute_nil itf_line, 'inspec-test-fixture should now appear in the output of inspec list'
|
||||
assert_match(/\s*inspec-test-fixture\s+0.1.0\s+gem\s+/, itf_line, 'list output should show that it is a gem installation with version')
|
||||
end
|
||||
|
||||
def test_fail_install_from_nonexistant_rubygem_version
|
||||
install_result = run_inspec_process_with_this_plugin('plugin install inspec-test-fixture -v 99.99.99')
|
||||
|
||||
assert_empty install_result.stderr
|
||||
assert_equal 1, install_result.exit_status, 'Exit status should be 1'
|
||||
|
||||
fail_message = install_result.stdout.split("\n").grep(/failed/).last
|
||||
refute_nil fail_message, 'Should find a failure message at the end'
|
||||
assert_includes fail_message, 'inspec-test-fixture'
|
||||
assert_includes fail_message, '99.99.99'
|
||||
assert_includes fail_message, 'no such version'
|
||||
assert_includes fail_message, 'on rubygems.org'
|
||||
end
|
||||
|
||||
def test_refuse_install_when_missing_prefix
|
||||
install_result = run_inspec_process_with_this_plugin('plugin install test-fixture')
|
||||
|
||||
assert_empty install_result.stderr
|
||||
assert_equal 1, install_result.exit_status, 'Exit status should be 1'
|
||||
|
||||
fail_message = install_result.stdout.split("\n").grep(/failed/).last
|
||||
refute_nil fail_message, 'Should find a failure message at the end'
|
||||
assert_includes fail_message, 'test-fixture'
|
||||
assert_includes fail_message, "All inspec plugins must begin with either 'inspec-' or 'train-'"
|
||||
end
|
||||
|
||||
def test_refuse_install_when_already_installed_same_version
|
||||
pre_block = Proc.new do |plugin_statefile_data, tmp_dir|
|
||||
plugin_statefile_data.clear # Signal not to write a file, we'll provide one.
|
||||
copy_in_core_config_dir('test-fixture-2-float', tmp_dir)
|
||||
end
|
||||
|
||||
install_result = run_inspec_process_with_this_plugin('plugin install inspec-test-fixture', pre_run: pre_block)
|
||||
assert_empty install_result.stderr
|
||||
assert_equal 2, install_result.exit_status, 'Exit status should be 2'
|
||||
|
||||
refusal_message = install_result.stdout.split("\n").grep(/refusing/).last
|
||||
refute_nil refusal_message, 'Should find a failure message at the end'
|
||||
assert_includes refusal_message, 'inspec-test-fixture'
|
||||
assert_includes refusal_message, '0.2.0'
|
||||
assert_includes refusal_message, 'Plugin already installed at latest version'
|
||||
end
|
||||
|
||||
def test_refuse_install_when_already_installed_can_update
|
||||
pre_block = Proc.new do |plugin_statefile_data, tmp_dir|
|
||||
plugin_statefile_data.clear # Signal not to write a file, we'll provide one.
|
||||
copy_in_core_config_dir('test-fixture-1-float', tmp_dir)
|
||||
end
|
||||
|
||||
install_result = run_inspec_process_with_this_plugin('plugin install inspec-test-fixture', pre_run: pre_block)
|
||||
assert_empty install_result.stderr
|
||||
assert_equal 2, install_result.exit_status, 'Exit status should be 2'
|
||||
|
||||
refusal_message = install_result.stdout.split("\n").grep(/refusing/).last
|
||||
refute_nil refusal_message, 'Should find a failure message at the end'
|
||||
assert_includes refusal_message, 'inspec-test-fixture'
|
||||
assert_includes refusal_message, '0.1.0'
|
||||
assert_includes refusal_message, '0.2.0'
|
||||
assert_includes refusal_message, 'Update required'
|
||||
assert_includes refusal_message, 'inspec plugin update'
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
#-----------------------------------------------------------------------------------------#
|
||||
# inspec plugin update
|
||||
#-----------------------------------------------------------------------------------------#
|
||||
class PluginManagerCliUpdate < MiniTest::Test
|
||||
include CorePluginFunctionalHelper
|
||||
include PluginManagerHelpers
|
||||
|
||||
def test_when_a_plugin_can_be_updated
|
||||
pre_block = Proc.new do |plugin_statefile_data, tmp_dir|
|
||||
plugin_statefile_data.clear # Signal not to write a file, we'll provide one.
|
||||
copy_in_core_config_dir('test-fixture-1-float', tmp_dir)
|
||||
end
|
||||
|
||||
update_result = run_inspec_process_with_this_plugin('plugin update inspec-test-fixture', pre_run: pre_block, post_run: list_after_run)
|
||||
assert_empty update_result.stderr
|
||||
assert_equal 0, update_result.exit_status, 'Exit status should be 0'
|
||||
|
||||
success_message = update_result.stdout.split("\n").grep(/updated/).last
|
||||
refute_nil success_message, 'Should find a success message at the end'
|
||||
assert_includes success_message, 'inspec-test-fixture'
|
||||
assert_includes success_message, '0.1.0'
|
||||
assert_includes success_message, '0.2.0'
|
||||
assert_includes success_message, 'updated from rubygems.org'
|
||||
|
||||
list_result = update_result.payload.list_result
|
||||
itf_line = list_result.stdout.split("\n").grep(/inspec-test-fixture/).first
|
||||
refute_nil itf_line, 'inspec-test-fixture should appear in the output of inspec list'
|
||||
assert_match(/\s*inspec-test-fixture\s+0.2.0\s+gem\s+/, itf_line, 'list output should show that it is a gem installation with version 0.2.0')
|
||||
end
|
||||
|
||||
def test_refuse_update_when_already_current
|
||||
pre_block = Proc.new do |plugin_statefile_data, tmp_dir|
|
||||
plugin_statefile_data.clear # Signal not to write a file, we'll provide one.
|
||||
copy_in_core_config_dir('test-fixture-2-float', tmp_dir)
|
||||
end
|
||||
|
||||
update_result = run_inspec_process_with_this_plugin('plugin update inspec-test-fixture', pre_run: pre_block)
|
||||
assert_empty update_result.stderr
|
||||
assert_equal 2, update_result.exit_status, 'Exit status should be 2'
|
||||
|
||||
refusal_message = update_result.stdout.split("\n").grep(/refusing/).last
|
||||
refute_nil refusal_message, 'Should find a failure message at the end'
|
||||
assert_includes refusal_message, 'inspec-test-fixture'
|
||||
assert_includes refusal_message, '0.2.0'
|
||||
assert_includes refusal_message, 'Already installed at latest version'
|
||||
end
|
||||
|
||||
def test_fail_update_from_nonexistant_gem
|
||||
update_result = run_inspec_process_with_this_plugin('plugin update inspec-test-fixture-nonesuch')
|
||||
|
||||
assert_empty update_result.stderr
|
||||
assert_equal 1, update_result.exit_status, 'Exit status should be 1'
|
||||
assert_match(/No such plugin installed: .+ - update failed/, update_result.stdout)
|
||||
end
|
||||
|
||||
def test_fail_update_path
|
||||
pre_block = Proc.new do |plugin_statefile_data, tmp_dir|
|
||||
plugin_statefile_data.clear # Signal not to write a file, we'll provide one.
|
||||
copy_in_core_config_dir('meaning_by_path', tmp_dir)
|
||||
end
|
||||
|
||||
update_result = run_inspec_process_with_this_plugin('plugin update inspec-meaning-of-life', pre_run: pre_block)
|
||||
assert_empty update_result.stderr
|
||||
assert_equal 2, update_result.exit_status, 'Exit status should be 2'
|
||||
|
||||
refusal_message = update_result.stdout.split("\n").grep(/refusing/).last
|
||||
refute_nil refusal_message, 'Should find a failure message at the end'
|
||||
assert_includes refusal_message, 'inspec-meaning-of-life'
|
||||
assert_includes refusal_message, 'inspec plugin uninstall'
|
||||
assert_includes refusal_message, 'Cannot update path-based install'
|
||||
end
|
||||
end
|
||||
|
||||
#-----------------------------------------------------------------------------------------#
|
||||
# inspec plugin uninstall
|
||||
#-----------------------------------------------------------------------------------------#
|
||||
class PluginManagerCliUninstall < MiniTest::Test
|
||||
include CorePluginFunctionalHelper
|
||||
include PluginManagerHelpers
|
||||
|
||||
def test_when_a_gem_plugin_can_be_uninstalled
|
||||
pre_block = Proc.new do |plugin_statefile_data, tmp_dir|
|
||||
plugin_statefile_data.clear # Signal not to write a file, we'll provide one.
|
||||
copy_in_core_config_dir('test-fixture-1-float', tmp_dir)
|
||||
end
|
||||
|
||||
# Attempt uninstall
|
||||
uninstall_result = run_inspec_process_with_this_plugin('plugin uninstall inspec-test-fixture', pre_run: pre_block, post_run: list_after_run)
|
||||
assert_empty uninstall_result.stderr
|
||||
assert_equal 0, uninstall_result.exit_status, 'Exit status should be 0'
|
||||
|
||||
success_message = uninstall_result.stdout.split("\n").grep(/uninstalled/).last
|
||||
refute_nil success_message, 'Should find a success message at the end'
|
||||
assert_includes success_message, 'inspec-test-fixture'
|
||||
assert_includes success_message, '0.1.0'
|
||||
assert_includes success_message, 'has been uninstalled'
|
||||
|
||||
list_result = uninstall_result.payload.list_result
|
||||
itf_line = list_result.stdout.split("\n").grep(/inspec-test-fixture/).first
|
||||
assert_nil itf_line, 'inspec-test-fixture should not appear in the output of inspec list'
|
||||
end
|
||||
|
||||
def test_when_a_path_plugin_can_be_uninstalled
|
||||
pre_block = Proc.new do |plugin_statefile_data, tmp_dir|
|
||||
plugin_statefile_data.clear # Signal not to write a file, we'll provide one.
|
||||
# This fixture includes a path install for inspec-meaning-of-life
|
||||
copy_in_core_config_dir('test-fixture-1-float', tmp_dir)
|
||||
end
|
||||
|
||||
uninstall_result = run_inspec_process_with_this_plugin('plugin uninstall inspec-meaning-of-life', pre_run: pre_block, post_run: list_after_run)
|
||||
assert_empty uninstall_result.stderr
|
||||
assert_equal 0, uninstall_result.exit_status, 'Exit status should be 0'
|
||||
|
||||
success_message = uninstall_result.stdout.split("\n").grep(/uninstalled/).last
|
||||
refute_nil success_message, 'Should find a success message at the end'
|
||||
assert_includes success_message, 'inspec-meaning-of-life'
|
||||
assert_includes success_message, 'path-based plugin install'
|
||||
assert_includes success_message, 'has been uninstalled'
|
||||
|
||||
list_result = uninstall_result.payload.list_result
|
||||
itf_line = list_result.stdout.split("\n").grep(/inspec-meaning-of-life/).first
|
||||
assert_nil itf_line, 'inspec-meaning-of-life should not appear in the output of inspec list'
|
||||
end
|
||||
|
||||
def test_fail_uninstall_from_plugin_that_is_not_installed
|
||||
uninstall_result = run_inspec_process_with_this_plugin('plugin uninstall inspec-test-fixture-nonesuch')
|
||||
|
||||
assert_empty uninstall_result.stderr
|
||||
assert_equal 1, uninstall_result.exit_status, 'Exit status should be 1'
|
||||
refute_includes 'Inspec::Plugin::V2::UnInstallError', uninstall_result.stdout # Stacktrace marker
|
||||
assert_match(/No such plugin installed: .+ - uninstall failed/, uninstall_result.stdout)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,71 @@
|
|||
require_relative '../../../shared/core_plugin_test_helper.rb'
|
||||
|
||||
#-----------------------------------------------------------------------#
|
||||
# Thor option defs
|
||||
#-----------------------------------------------------------------------#
|
||||
class PluginManagerCliOptions < MiniTest::Test
|
||||
include CorePluginUnitHelper
|
||||
let(:cli_class) { InspecPlugins::PluginManager::CliCommand }
|
||||
|
||||
def setup
|
||||
require_relative '../../lib/inspec-plugin-manager-cli/cli_command'
|
||||
end
|
||||
|
||||
def test_list_args
|
||||
arg_config = cli_class.all_commands['list'].options
|
||||
assert_equal 1, arg_config.count, 'The list command should have 1 option'
|
||||
|
||||
assert_includes arg_config.keys, :all, 'The list command should have an --all option'
|
||||
assert_equal :boolean, arg_config[:all].type, 'The --all option should be boolean'
|
||||
assert_equal :a, arg_config[:all].aliases.first, 'The --all option should be aliased as -a'
|
||||
refute_nil arg_config[:all].description, 'The --all option should have a description'
|
||||
refute arg_config[:all].required, 'The --all option should not be required'
|
||||
|
||||
assert_equal 0, cli_class.instance_method(:list).arity, 'The list command should take no arguments'
|
||||
end
|
||||
|
||||
def test_search_args
|
||||
arg_config = cli_class.all_commands['search'].options
|
||||
assert_equal 2, arg_config.count, 'The search command should have 2 options'
|
||||
|
||||
assert_includes arg_config.keys, :all, 'The search command should have an --all option'
|
||||
assert_equal :boolean, arg_config[:all].type, 'The --all option should be boolean'
|
||||
assert_equal :a, arg_config[:all].aliases.first, 'The --all option should be aliased as -a'
|
||||
refute_nil arg_config[:all].description, 'The --all option should have a description'
|
||||
refute arg_config[:all].required, 'The --all option should not be required'
|
||||
|
||||
assert_includes arg_config.keys, :exact, 'The search command should have an --exact option'
|
||||
assert_equal :boolean, arg_config[:exact].type, 'The --exact option should be boolean'
|
||||
assert_equal :e, arg_config[:exact].aliases.first, 'The --exact option should be aliased as -e'
|
||||
refute_nil arg_config[:exact].description, 'The --exact option should have a description'
|
||||
refute arg_config[:exact].required, 'The --exact option should not be required'
|
||||
|
||||
assert_equal 1, cli_class.instance_method(:search).arity, 'The search command should take one argument'
|
||||
end
|
||||
|
||||
def test_install_args
|
||||
arg_config = cli_class.all_commands['install'].options
|
||||
assert_equal 1, arg_config.count, 'The install command should have 1 option'
|
||||
|
||||
assert_includes arg_config.keys, :version, 'The install command should have a --version option'
|
||||
assert_equal :string, arg_config[:version].type, 'The --version option should be a string'
|
||||
assert_equal :v, arg_config[:version].aliases.first, 'The --version option should be aliased as -v'
|
||||
refute_nil arg_config[:version].description, 'The --version option should have a description'
|
||||
refute arg_config[:version].required, 'The --version option should not be required'
|
||||
|
||||
assert_equal 1, cli_class.instance_method(:install).arity, 'The install command should take one argument'
|
||||
end
|
||||
|
||||
def test_update_args
|
||||
# TODO: allow specifying version
|
||||
arg_config = cli_class.all_commands['update'].options
|
||||
assert_equal 0, arg_config.count, 'The update command should have no options'
|
||||
assert_equal 1, cli_class.instance_method(:update).arity, 'The update command should take one argument'
|
||||
end
|
||||
|
||||
def test_uninstall_args
|
||||
arg_config = cli_class.all_commands['uninstall'].options
|
||||
assert_equal 0, arg_config.count, 'The uninstall command should have no options'
|
||||
assert_equal 1, cli_class.instance_method(:uninstall).arity, 'The uninstall command should take one argument'
|
||||
end
|
||||
end
|
|
@ -0,0 +1,20 @@
|
|||
require_relative '../../../shared/core_plugin_test_helper.rb'
|
||||
|
||||
#-----------------------------------------------------------------------#
|
||||
# Plugin Definition
|
||||
#-----------------------------------------------------------------------#
|
||||
class PluginManagerCliDefinitionTests < MiniTest::Test
|
||||
include CorePluginUnitHelper
|
||||
|
||||
def test_plugin_registered
|
||||
loader = Inspec::Plugin::V2::Loader.new
|
||||
loader.load_all # We want to ensure it is auto-loaded
|
||||
|
||||
assert registry.known_plugin?(:'inspec-plugin-manager-cli'), 'inspec-plugin-manager-cli should be registered'
|
||||
assert registry.loaded_plugin?(:'inspec-plugin-manager-cli'), 'inspec-plugin-manager-cli should be loaded'
|
||||
|
||||
status = registry[:'inspec-plugin-manager-cli']
|
||||
assert_equal 2, status.api_generation, 'inspec-plugin-manager-cli should be v2'
|
||||
assert_includes status.plugin_types, :cli_command, 'inspec-plugin-manager-cli should have cli_command activators'
|
||||
end
|
||||
end
|
|
@ -11,12 +11,32 @@ require 'ostruct'
|
|||
|
||||
# Utilities often needed
|
||||
require 'fileutils'
|
||||
require 'tmpdir'
|
||||
require 'pathname'
|
||||
require 'forwardable'
|
||||
|
||||
# Configure MiniTest to expose things like `let`
|
||||
class Module
|
||||
include Minitest::Spec::DSL
|
||||
end
|
||||
|
||||
module Inspec
|
||||
class FuncTestRunResult
|
||||
attr_reader :train_result
|
||||
attr_reader :payload
|
||||
|
||||
extend Forwardable
|
||||
def_delegator :train_result, :stdout
|
||||
def_delegator :train_result, :stderr
|
||||
def_delegator :train_result, :exit_status
|
||||
|
||||
def initialize(train_result)
|
||||
@train_result = train_result
|
||||
@payload = OpenStruct.new
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
module CorePluginBaseHelper
|
||||
let(:repo_path) { File.expand_path(File.join(__FILE__, '..', '..', '..', '..')) }
|
||||
let(:inspec_bin_path) { File.join(repo_path, 'bin', 'inspec') }
|
||||
|
@ -40,11 +60,90 @@ module CorePluginFunctionalHelper
|
|||
elsif opts.key?(:env)
|
||||
prefix = opts[:env].to_a.map { |assignment| "#{assignment[0]}=#{assignment[1]}" }.join(' ')
|
||||
end
|
||||
TRAIN_CONNECTION.run_command("#{prefix} #{inspec_bin_path} #{command_line}")
|
||||
Inspec::FuncTestRunResult.new(TRAIN_CONNECTION.run_command("#{prefix} #{inspec_bin_path} #{command_line}"))
|
||||
end
|
||||
|
||||
# This helper does some fancy footwork to make InSpec think a plugin
|
||||
# under development is temporarily installed.
|
||||
# @param String command_line Invocation, without the word 'inspec'
|
||||
# @param Hash opts options as for run_inspec_process, with more options:
|
||||
# :pre_run: Proc(plugin_statefile_data, tmp_dir_path) - optional setup block.
|
||||
# Modify plugin_statefile_data as needed; it will be written to a plugins.json
|
||||
# in tmp_dir_path. You may also copy in other things to the tmp_dir_path. Your PWD
|
||||
# will be in the tmp_dir, and it will exist and be empty.
|
||||
# :post_run: Proc(FuncTestRunResult, tmp_dir_path) - optional result capture block.
|
||||
# run_result will be populated, but you can add more to the ostruct .payload
|
||||
# Your PWD will be the tmp_dir, and it will still exist (for a moment!)
|
||||
def run_inspec_process_with_this_plugin(command_line, opts = {})
|
||||
plugin_path = __find_plugin_path_from_caller
|
||||
|
||||
# If it looks like it is a core plugin under test, don't add it to the plugin file
|
||||
# since the loader will auto-load it anyway
|
||||
if plugin_path.include?('lib/plugins/inspec-')
|
||||
plugin_file_data = __make_empty_plugin_file_data_structure
|
||||
else
|
||||
plugin_file_data = __make_plugin_file_data_structure_with_path(plugin_path)
|
||||
end
|
||||
|
||||
Dir.mktmpdir do |tmp_dir|
|
||||
opts[:pre_run]&.call(plugin_file_data, tmp_dir)
|
||||
plugin_file_path = File.join(tmp_dir, 'plugins.json')
|
||||
# HACK: If the block cleared the hash, take that to mean it will provide a plugins.json file of its own.
|
||||
File.write(plugin_file_path, JSON.generate(plugin_file_data)) unless plugin_file_data.empty?
|
||||
opts[:env] ||= {}
|
||||
opts[:env]['INSPEC_CONFIG_DIR'] = tmp_dir
|
||||
run_result = run_inspec_process(command_line, opts)
|
||||
|
||||
# Read the resulting plugins.json into memory, if any
|
||||
if File.exist?(plugin_file_path)
|
||||
run_result.payload.plugin_data = JSON.parse(File.read(plugin_file_path))
|
||||
end
|
||||
|
||||
opts[:post_run]&.call(run_result, tmp_dir)
|
||||
run_result
|
||||
end
|
||||
end
|
||||
|
||||
def __find_plugin_path_from_caller(frames_back = 2)
|
||||
caller_path = Pathname.new(caller_locations(frames_back, 1).first.absolute_path)
|
||||
# Typical caller path:
|
||||
# /Users/cwolfe/sandbox/inspec-resource-lister/test/functional/inspec_resource_lister_test.rb
|
||||
# We want:
|
||||
# /Users/cwolfe/sandbox/inspec-resource-lister/lib/inspec-resource-lister.rb
|
||||
cursor = caller_path
|
||||
until cursor.basename.to_s == 'test' && cursor.parent.basename.to_s =~ /^(inspec|train)-/
|
||||
cursor = cursor.parent
|
||||
break if cursor.nil?
|
||||
end
|
||||
raise 'Could not comprehend plugin project directory structure' if cursor.nil?
|
||||
|
||||
project_dir = cursor.parent
|
||||
plugin_name = project_dir.basename
|
||||
entry_point = File.join(project_dir.to_s, 'lib', plugin_name.to_s + '.rb')
|
||||
raise 'Could not find plugin entry point' unless File.exist?(entry_point)
|
||||
entry_point
|
||||
end
|
||||
|
||||
def __make_plugin_file_data_structure_with_path(path)
|
||||
# TODO: dry this up, refs #3350
|
||||
plugin_name = File.basename(path, '.rb')
|
||||
data = __make_empty_plugin_file_data_structure
|
||||
data['plugins'] << {
|
||||
'name' => plugin_name,
|
||||
'installation_type' => 'path',
|
||||
'installation_path' => path,
|
||||
}
|
||||
end
|
||||
|
||||
def __make_empty_plugin_file_data_structure
|
||||
# TODO: dry this up, refs #3350
|
||||
{
|
||||
'plugins_config_version' => '1.0.0',
|
||||
'plugins' => [],
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
module CorePluginUnitHelper
|
||||
include CorePluginBaseHelper
|
||||
require 'inspec'
|
||||
end
|
||||
|
|
|
@ -80,3 +80,9 @@ describe 'cli command plugins' do
|
|||
outcome.stdout.must_include 'inspec meaningoflife'
|
||||
end
|
||||
end
|
||||
|
||||
#=========================================================================================#
|
||||
# inspec plugin command
|
||||
#=========================================================================================#
|
||||
|
||||
# See lib/plugins/inspec-plugin-manager-cli/test
|
||||
|
|
|
@ -3,6 +3,11 @@
|
|||
"plugins": [
|
||||
{
|
||||
"name": "inspec-test-fixture"
|
||||
},
|
||||
{
|
||||
"name": "inspec-meaning-of-life",
|
||||
"installation_type": "path",
|
||||
"installation_path": "test/unit/mock/plugins/meaning_of_life_path_mode/inspec-meaning-of-life"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -1,2 +1,2 @@
|
|||
require 'inspec-test-fixture/version'
|
||||
require 'inspec-test-fixture/plugin'
|
||||
require_relative 'inspec-test-fixture/version'
|
||||
require_relative 'inspec-test-fixture/plugin'
|
||||
|
|
|
@ -5,7 +5,7 @@ module InspecPlugins
|
|||
plugin_name :'inspec-test-fixture'
|
||||
|
||||
mock_plugin_type :'inspec-test-fixture' do
|
||||
require 'mock_plugin'
|
||||
require_relative 'mock_plugin'
|
||||
InspecPlugins::TestFixture
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,13 +4,13 @@ module InspecPlugins
|
|||
class Plugin < Inspec.plugin(2)
|
||||
plugin_name :'inspec-meaning-of-life'
|
||||
|
||||
mock_plugin_type 'meaning-of-life-the-universe-and-everything' do
|
||||
mock_plugin_type :'meaning-of-life-the-universe-and-everything' do
|
||||
# NOTE: we can't use require, because these test files are repeatedly reloaded
|
||||
load 'test/unit/mock/plugins/meaning_of_life_path_mode/inspec-meaning-of-life/mock_plugin.rb'
|
||||
InspecPlugins::MeaningOfLife::MockPlugin
|
||||
end
|
||||
|
||||
cli_command 'meaningoflife' do
|
||||
cli_command :'meaningoflife' do
|
||||
# NOTE: we can't use require, because these test files are repeatedly reloaded
|
||||
load 'test/unit/mock/plugins/meaning_of_life_path_mode/inspec-meaning-of-life/cli_command.rb'
|
||||
InspecPlugins::MeaningOfLife::CliCommand
|
||||
|
|
|
@ -23,8 +23,8 @@ class PluginLoaderTests < MiniTest::Test
|
|||
|
||||
@config_dir_path = File.join(mock_path, 'config_dirs')
|
||||
@bundled_plugins = [
|
||||
:compliance,
|
||||
:supermarket,
|
||||
:'inspec-compliance',
|
||||
:'inspec-supermarket',
|
||||
]
|
||||
@core_plugins = [
|
||||
:'inspec-artifact',
|
||||
|
|
Loading…
Reference in a new issue