mirror of
https://github.com/inspec/inspec
synced 2025-02-17 06:28:40 +00:00
Create a class to handle the plugins.json file (#3575)
* unit tests for plugin conf file class, all skip * File path stuff works * Validation works * Add works * Added remove_entry * Save works - ready to refactor others * Rework Loader to use ConfigFile * Modify loader and installer to use the config file class * linting Signed-off-by: Clinton Wolfe <clintoncwolfe@gmail.com>
This commit is contained in:
parent
bf5815ed78
commit
3c8697e5e2
20 changed files with 686 additions and 174 deletions
|
@ -24,6 +24,7 @@ module Inspec
|
|||
end
|
||||
|
||||
require 'inspec/globals'
|
||||
require 'inspec/plugin/v2/config_file'
|
||||
require 'inspec/plugin/v2/registry'
|
||||
require 'inspec/plugin/v2/loader'
|
||||
require 'inspec/plugin/v2/plugin_base'
|
||||
|
|
148
lib/inspec/plugin/v2/config_file.rb
Normal file
148
lib/inspec/plugin/v2/config_file.rb
Normal file
|
@ -0,0 +1,148 @@
|
|||
require 'json'
|
||||
|
||||
module Inspec::Plugin::V2
|
||||
# Represents the plugin config file on disk.
|
||||
class ConfigFile
|
||||
include Enumerable
|
||||
|
||||
attr_reader :path
|
||||
|
||||
def initialize(path = nil)
|
||||
@path = path || self.class.default_path
|
||||
@data = blank_structure
|
||||
|
||||
read_and_validate_file if File.exist?(@path)
|
||||
end
|
||||
|
||||
# Returns the defaut path for a config file.
|
||||
# This respects ENV['INSPEC_CONFIG_DIR'].
|
||||
def self.default_path
|
||||
File.join(Inspec.config_dir, 'plugins.json')
|
||||
end
|
||||
|
||||
# Implement Enumerable. All Enumerable methds act
|
||||
# on the plugins list, and yield Hashes that represent
|
||||
# an entry.
|
||||
def each(&block)
|
||||
@data[:plugins].each(&block)
|
||||
end
|
||||
|
||||
# Look for a plugin by name.
|
||||
def plugin_by_name(name)
|
||||
detect { |entry| entry[:name] == name.to_sym }
|
||||
end
|
||||
|
||||
# Check for a plugin
|
||||
def existing_entry?(name)
|
||||
!plugin_by_name(name).nil?
|
||||
end
|
||||
|
||||
# Add an entry with full validation.
|
||||
def add_entry(proposed_entry)
|
||||
unless proposed_entry.keys.all? { |field| field.is_a? Symbol }
|
||||
raise Inspec::Plugin::V2::ConfigError, 'All keys to ConfigFile#add_entry must be symbols'
|
||||
end
|
||||
|
||||
validate_entry(proposed_entry)
|
||||
|
||||
if existing_entry?(proposed_entry[:name])
|
||||
raise Inspec::Plugin::V2::ConfigError, "Duplicate plugin name in call to ConfigFile#add_entry: '#{proposed_entry[:name]}'"
|
||||
end
|
||||
|
||||
@data[:plugins] << proposed_entry
|
||||
end
|
||||
|
||||
# Removes an entry specified by plugin name.
|
||||
def remove_entry(name)
|
||||
unless existing_entry?(name)
|
||||
raise Inspec::Plugin::V2::ConfigError, "No such entry with plugin name '#{name}'"
|
||||
end
|
||||
@data[:plugins].delete_if { |entry| entry[:name] == name.to_sym }
|
||||
end
|
||||
|
||||
# Save the file to disk as a JSON structure at the path.
|
||||
def save
|
||||
dir = File.dirname(path)
|
||||
FileUtils.mkdir_p(dir)
|
||||
File.write(path, JSON.pretty_generate(@data))
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def blank_structure
|
||||
{
|
||||
plugins_config_version: '1.0.0',
|
||||
plugins: [],
|
||||
}
|
||||
end
|
||||
|
||||
def read_and_validate_file
|
||||
@data = JSON.parse(File.read(path), symbolize_names: true)
|
||||
validate_file
|
||||
rescue JSON::ParserError => e
|
||||
raise Inspec::Plugin::V2::ConfigError, "Failed to load plugins JSON configuration from #{path}:\n#{e}"
|
||||
end
|
||||
|
||||
def validate_file # rubocop: disable Metrics/AbcSize
|
||||
unless @data[:plugins_config_version]
|
||||
raise Inspec::Plugin::V2::ConfigError, "Missing 'plugins_config_version' entry at #{path} - currently support versions: 1.0.0"
|
||||
end
|
||||
|
||||
unless @data[:plugins_config_version] == '1.0.0'
|
||||
raise Inspec::Plugin::V2::ConfigError, "Unsupported plugins.json file version #{@data[:plugins_config_version]} at #{path} - currently support versions: 1.0.0"
|
||||
end
|
||||
|
||||
plugin_entries = @data[:plugins]
|
||||
if plugin_entries.nil?
|
||||
raise Inspec::Plugin::V2::ConfigError, "Malformed plugins.json file at #{path} - missing top-level key named 'plugins', whose value should be an array"
|
||||
end
|
||||
|
||||
unless plugin_entries.is_a?(Array)
|
||||
raise Inspec::Plugin::V2::ConfigError, "Malformed plugins.json file at #{path} - top-level key named 'plugins' should be an array"
|
||||
end
|
||||
|
||||
plugin_entries.each_with_index do |plugin_entry, idx|
|
||||
begin
|
||||
validate_entry(plugin_entry)
|
||||
rescue Inspec::Plugin::V2::ConfigError => ex
|
||||
# append some context to the message
|
||||
raise Inspec::Plugin::V2::ConfigError, 'Malformed plugins.json file - ' + ex.message + " at index #{idx}"
|
||||
end
|
||||
|
||||
# Check for duplicates
|
||||
plugin_entries.each_with_index do |other_entry, other_idx|
|
||||
next if idx == other_idx
|
||||
next unless other_entry.is_a? Hash # We'll catch that invalid entry later
|
||||
next if plugin_entry[:name] != other_entry[:name]
|
||||
indices = [idx, other_idx].sort
|
||||
raise Inspec::Plugin::V2::ConfigError, "Malformed plugins.json file - duplicate plugin entry '#{plugin_entry[:name]}' detected at index #{indices[0]} and #{indices[1]}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def validate_entry(plugin_entry)
|
||||
unless plugin_entry.is_a? Hash
|
||||
raise Inspec::Plugin::V2::ConfigError, "each 'plugins' entry should be a Hash / JSON object"
|
||||
end
|
||||
|
||||
unless plugin_entry.key? :name
|
||||
raise Inspec::Plugin::V2::ConfigError, "'plugins' entry missing 'name' field"
|
||||
end
|
||||
|
||||
# Symbolize the name.
|
||||
plugin_entry[:name] = plugin_entry[:name].to_sym
|
||||
|
||||
if plugin_entry.key? :installation_type
|
||||
seen_type = plugin_entry[:installation_type]
|
||||
unless [:gem, :path].include? seen_type.to_sym
|
||||
raise Inspec::Plugin::V2::ConfigError, "'plugins' entry with unrecognized installation_type (must be one of 'gem' or 'path')"
|
||||
end
|
||||
plugin_entry[:installation_type] = seen_type.to_sym
|
||||
|
||||
if plugin_entry[:installation_type] == :path && !plugin_entry.key?(:installation_path)
|
||||
raise Inspec::Plugin::V2::ConfigError, "'plugins' entry with a 'path' installation_type missing installation path"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -24,7 +24,7 @@ module Inspec::Plugin::V2
|
|||
|
||||
Gem.configuration['verbose'] = false
|
||||
|
||||
attr_reader :loader, :registry
|
||||
attr_reader :conf_file, :loader, :registry
|
||||
def_delegator :loader, :plugin_gem_path, :gem_path
|
||||
def_delegator :loader, :plugin_conf_file_path
|
||||
def_delegator :loader, :list_managed_gems
|
||||
|
@ -459,45 +459,25 @@ module Inspec::Plugin::V2
|
|||
#===================================================================#
|
||||
# plugins.json Maintenance Methods #
|
||||
#===================================================================#
|
||||
|
||||
# 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
|
||||
# Be careful no to initialize this until just before we write.
|
||||
# Under testing, ENV['INSPEC_CONFIG_DIR'] may have changed.
|
||||
@conf_file = Inspec::Plugin::V2::ConfigFile.new
|
||||
|
||||
# TODO: refactor the plugin.json file to have its own class, which Installer consumes
|
||||
def update_plugin_config_data(plugin_name, opts)
|
||||
config = read_or_init_config_data
|
||||
config['plugins'].delete_if { |entry| entry['name'] == plugin_name }
|
||||
return config if opts[:action] == :uninstall
|
||||
|
||||
entry = { 'name' => plugin_name }
|
||||
|
||||
# Parsing by Requirement handles lot of awkward formattoes
|
||||
entry['version'] = Gem::Requirement.new(opts[:version]).to_s if opts.key?(:version)
|
||||
|
||||
if opts.key?(:path)
|
||||
entry['installation_type'] = 'path'
|
||||
entry['installation_path'] = opts[:path]
|
||||
# Remove, then optionally rebuild, the entry for the plugin being modified.
|
||||
conf_file.remove_entry(plugin_name) if conf_file.existing_entry?(plugin_name)
|
||||
unless opts[:action] == :uninstall
|
||||
entry = { name: plugin_name }
|
||||
# Parsing by Requirement handles lot of awkward formattoes
|
||||
entry[:version] = Gem::Requirement.new(opts[:version]).to_s if opts.key?(:version)
|
||||
if opts.key?(:path)
|
||||
entry[:installation_type] = :path
|
||||
entry[:installation_path] = opts[:path]
|
||||
end
|
||||
conf_file.add_entry(entry)
|
||||
end
|
||||
|
||||
config['plugins'] << entry
|
||||
config
|
||||
end
|
||||
|
||||
# TODO: check for validity
|
||||
# TODO: refactor the plugin.json file to have its own class, which Installer consumes
|
||||
def read_or_init_config_data
|
||||
if File.exist?(plugin_conf_file_path)
|
||||
JSON.parse(File.read(plugin_conf_file_path))
|
||||
else
|
||||
{
|
||||
'plugins_config_version' => '1.0.0',
|
||||
'plugins' => [],
|
||||
}
|
||||
end
|
||||
conf_file.save
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
require 'json'
|
||||
require 'inspec/log'
|
||||
require 'inspec/plugin/v2/config_file'
|
||||
|
||||
# Add the current directory of the process to the load path
|
||||
$LOAD_PATH.unshift('.') unless $LOAD_PATH.include?('.')
|
||||
|
@ -9,13 +9,13 @@ $LOAD_PATH.unshift(folder) unless $LOAD_PATH.include?('folder')
|
|||
|
||||
module Inspec::Plugin::V2
|
||||
class Loader
|
||||
attr_reader :registry, :options
|
||||
attr_reader :conf_file, :registry, :options
|
||||
|
||||
def initialize(options = {})
|
||||
@options = options
|
||||
@registry = Inspec::Plugin::V2::Registry.instance
|
||||
read_conf_file
|
||||
unpack_conf_file
|
||||
@conf_file = Inspec::Plugin::V2::ConfigFile.new
|
||||
read_conf_file_into_registry
|
||||
|
||||
# Old-style (v0, v1) co-distributed plugins were called 'bundles'
|
||||
# and were located in lib/bundles
|
||||
|
@ -157,16 +157,6 @@ module Inspec::Plugin::V2
|
|||
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
|
||||
end
|
||||
|
||||
# TODO: refactor the plugin.json file to have its own class, which Loader consumes
|
||||
def self.plugin_conf_file_path
|
||||
File.join(Inspec.config_dir, 'plugins.json')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# 'Activating' a gem adds it to the load path, so 'require "gemname"' will work.
|
||||
|
@ -271,71 +261,22 @@ module Inspec::Plugin::V2
|
|||
end
|
||||
end
|
||||
|
||||
# TODO: DRY up re: Installer read_or_init_config_file
|
||||
# TODO: refactor the plugin.json file to have its own class, which Loader consumes
|
||||
def read_conf_file
|
||||
if File.exist?(plugin_conf_file_path)
|
||||
@plugin_file_contents = JSON.parse(File.read(plugin_conf_file_path))
|
||||
else
|
||||
@plugin_file_contents = {
|
||||
'plugins_config_version' => '1.0.0',
|
||||
'plugins' => [],
|
||||
}
|
||||
end
|
||||
rescue JSON::ParserError => e
|
||||
raise Inspec::Plugin::V2::ConfigError, "Failed to load plugins JSON configuration from #{plugin_conf_file_path}:\n#{e}"
|
||||
end
|
||||
|
||||
# TODO: refactor the plugin.json file to have its own class, which Loader consumes
|
||||
def unpack_conf_file
|
||||
validate_conf_file
|
||||
@plugin_file_contents['plugins'].each do |plugin_json|
|
||||
def read_conf_file_into_registry
|
||||
conf_file.each do |plugin_entry|
|
||||
status = Inspec::Plugin::V2::Status.new
|
||||
status.name = plugin_json['name'].to_sym
|
||||
status.name = plugin_entry[:name]
|
||||
status.loaded = false
|
||||
status.installation_type = (plugin_json['installation_type'] || :gem).to_sym
|
||||
status.installation_type = (plugin_entry[:installation_type] || :gem)
|
||||
case status.installation_type
|
||||
when :gem
|
||||
status.entry_point = status.name.to_s
|
||||
status.version = plugin_json['version']
|
||||
status.version = plugin_entry[:version]
|
||||
when :path
|
||||
status.entry_point = plugin_json['installation_path']
|
||||
status.entry_point = plugin_entry[:installation_path]
|
||||
end
|
||||
|
||||
registry[status.name] = status
|
||||
end
|
||||
end
|
||||
|
||||
# TODO: refactor the plugin.json file to have its own class, which Loader consumes
|
||||
def validate_conf_file
|
||||
unless @plugin_file_contents['plugins_config_version'] == '1.0.0'
|
||||
raise Inspec::Plugin::V2::ConfigError, "Unsupported plugins.json file version #{@plugin_file_contents['plugins_config_version']} at #{plugin_conf_file_path} - currently support versions: 1.0.0"
|
||||
end
|
||||
|
||||
plugin_entries = @plugin_file_contents['plugins']
|
||||
unless plugin_entries.is_a?(Array)
|
||||
raise Inspec::Plugin::V2::ConfigError, "Malformed plugins.json file - should have a top-level key named 'plugins', whose value is an array"
|
||||
end
|
||||
|
||||
plugin_entries.each do |plugin_entry|
|
||||
unless plugin_entry.is_a? Hash
|
||||
raise Inspec::Plugin::V2::ConfigError, "Malformed plugins.json file - each 'plugins' entry should be a Hash / JSON object"
|
||||
end
|
||||
|
||||
unless plugin_entry.key? 'name'
|
||||
raise Inspec::Plugin::V2::ConfigError, "Malformed plugins.json file - each 'plugins' entry must have a 'name' field"
|
||||
end
|
||||
|
||||
next unless plugin_entry.key?('installation_type')
|
||||
unless %w{gem path}.include? plugin_entry['installation_type']
|
||||
raise Inspec::Plugin::V2::ConfigError, "Malformed plugins.json file - each 'installation_type' must be one of 'gem' or 'path'"
|
||||
end
|
||||
|
||||
next unless plugin_entry['installation_type'] == 'path'
|
||||
unless plugin_entry.key?('installation_path')
|
||||
raise Inspec::Plugin::V2::ConfigError, "Malformed plugins.json file - each 'plugins' entry with a 'path' installation_type must provide an 'installation_path' field"
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -7,31 +7,6 @@ require 'functional/helper'
|
|||
describe 'plugin loader' do
|
||||
include FunctionalHelper
|
||||
|
||||
it 'handles a corrupt plugins.json correctly' do
|
||||
outcome = inspec_with_env('version', INSPEC_CONFIG_DIR: File.join(config_dir_path, 'corrupt'))
|
||||
outcome.exit_status.must_equal 2
|
||||
outcome.stdout.wont_include('Inspec::Plugin::V2::ConfigError', 'No stacktrace in error by default')
|
||||
outcome.stdout.must_include('Failed to load plugins JSON configuration', 'Friendly message in error')
|
||||
outcome.stdout.must_include('unit/mock/config_dirs/corrupt/plugins.json', 'Location of bad file in error')
|
||||
|
||||
outcome = inspec_with_env('version --debug', INSPEC_CONFIG_DIR: File.join(config_dir_path, 'corrupt'))
|
||||
outcome.exit_status.must_equal 2
|
||||
outcome.stdout.must_include('Inspec::Plugin::V2::ConfigError', 'Include stacktrace in error with --debug')
|
||||
end
|
||||
|
||||
it 'handles a misversioned plugins.json correctly' do
|
||||
outcome = inspec_with_env('version', INSPEC_CONFIG_DIR: File.join(config_dir_path, 'bad_plugin_conf_version'))
|
||||
outcome.exit_status.must_equal 2
|
||||
outcome.stdout.wont_include('Inspec::Plugin::V2::ConfigError', 'No stacktrace in error by default')
|
||||
outcome.stdout.must_include('Unsupported plugins.json file version', 'Friendly message in error')
|
||||
outcome.stdout.must_include('unit/mock/config_dirs/bad_plugin_conf_version/plugins.json', 'Location of bad file in error')
|
||||
outcome.stdout.must_include('99.99.9', 'Incorrect version in error')
|
||||
|
||||
outcome = inspec_with_env('version --debug', INSPEC_CONFIG_DIR: File.join(config_dir_path, 'bad_plugin_conf_version'))
|
||||
outcome.exit_status.must_equal 2
|
||||
outcome.stdout.must_include('Inspec::Plugin::V2::ConfigError', 'Include stacktrace in error with --debug')
|
||||
end
|
||||
|
||||
it 'handles an unloadable plugin correctly' do
|
||||
outcome = inspec_with_env('version', INSPEC_CONFIG_DIR: File.join(config_dir_path, 'plugin_error_on_load'))
|
||||
outcome.exit_status.must_equal 2
|
||||
|
|
18
test/unit/mock/config_dirs/plugin_config_files/basic.json
Normal file
18
test/unit/mock/config_dirs/plugin_config_files/basic.json
Normal file
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"plugins_config_version" : "1.0.0",
|
||||
"plugins": [
|
||||
{
|
||||
"name": "inspec-test-fixture-00"
|
||||
},
|
||||
{
|
||||
"name": "inspec-test-fixture-01",
|
||||
"installation_type": "gem",
|
||||
"version": "0.2.0"
|
||||
},
|
||||
{
|
||||
"name": "inspec-test-fixture-02",
|
||||
"installation_type": "path",
|
||||
"installation_path": "/somewhere/on/disk/lib/entrypoint.rb"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
{
|
||||
"plugins_config_version" : "1.0.0",
|
||||
"plugins": [
|
||||
{
|
||||
"name": "inspec-test-fixture-00"
|
||||
},
|
||||
{
|
||||
"name": "inspec-test-fixture-01",
|
||||
"installation_type": "superglued",
|
||||
"version": "0.2.0"
|
||||
},
|
||||
{
|
||||
"name": "inspec-test-fixture-03",
|
||||
"installation_type": "path",
|
||||
"installation_path": "/somewhere/on/disk/lib/entrypoint.rb"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"plugins_config_version" : "1.0.0",
|
||||
"plugins": [
|
||||
{
|
||||
"name": "inspec-test-fixture-00"
|
||||
},
|
||||
{
|
||||
"name": "inspec-test-fixture-01",
|
||||
"installation_type": "gem",
|
||||
"version": "0.2.0"
|
||||
},
|
||||
{
|
||||
"name": "inspec-test-fixture-03",
|
||||
"installation_type": "path",
|
||||
"installation_path": "/somewhere/on/disk/lib/entrypoint.rb"
|
||||
},
|
||||
{
|
||||
"name": "inspec-test-fixture-01"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"plugins_config_version" : "1.0.0",
|
||||
"plugins": [
|
||||
{
|
||||
"name": "inspec-test-fixture-00"
|
||||
},
|
||||
{
|
||||
"installation_type": "gem",
|
||||
"version": "0.2.0"
|
||||
},
|
||||
{
|
||||
"name": "inspec-test-fixture-03",
|
||||
"installation_type": "path",
|
||||
"installation_path": "/somewhere/on/disk/lib/entrypoint.rb"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
{
|
||||
"plugins_config_version" : "1.0.0",
|
||||
"plugins": [
|
||||
{
|
||||
"name": "inspec-test-fixture-00"
|
||||
},
|
||||
{
|
||||
"name": "inspec-test-fixture-01",
|
||||
"installation_type": "gem",
|
||||
"version": "0.2.0"
|
||||
},
|
||||
{
|
||||
"name": "inspec-test-fixture-03",
|
||||
"installation_type": "path"
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"plugins_config_version" : "1.0.0",
|
||||
"plugins": [
|
||||
{
|
||||
"name": "inspec-test-fixture-00"
|
||||
},
|
||||
{
|
||||
"name": "inspec-test-fixture-01",
|
||||
"installation_type": "gem",
|
||||
"version": "0.2.0"
|
||||
},
|
||||
"inspec-test-fixture-03"
|
||||
]
|
||||
}
|
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"plugins_config_version" : "1.0.0",
|
||||
"plugins": {}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"plugins": []
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"plugins_config_version" : "1.0.0"
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"plugins_config_version" : "1.0.0",
|
||||
"plugins": [
|
||||
]
|
||||
}
|
|
@ -56,8 +56,6 @@ module InstallerTestHelpers
|
|||
|
||||
# Clean up any activated gems
|
||||
Gem.loaded_specs.delete('inspec-test-fixture')
|
||||
|
||||
# TODO: may need to edit the $LOAD_PATH, if it turns out that we need to "deactivate" gems after installation
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -141,18 +139,10 @@ class PluginInstallerInstallationTests < MiniTest::Test
|
|||
# Should now be present in plugin.json
|
||||
plugin_json_path = File.join(ENV['INSPEC_CONFIG_DIR'], 'plugins.json')
|
||||
assert File.exist?(plugin_json_path), 'plugins.json should now exist'
|
||||
plugin_json_data = JSON.parse(File.read(plugin_json_path))
|
||||
config_file = Inspec::Plugin::V2::ConfigFile.new(plugin_json_path)
|
||||
|
||||
assert_includes plugin_json_data.keys, 'plugins_config_version'
|
||||
assert_equal '1.0.0', plugin_json_data['plugins_config_version'], 'Plugin config version should ve initted to 1.0.0'
|
||||
assert_includes plugin_json_data.keys, 'plugins'
|
||||
assert_kind_of Array, plugin_json_data['plugins']
|
||||
assert_equal 1, plugin_json_data['plugins'].count, 'plugins.json should have one entry'
|
||||
entry = plugin_json_data['plugins'].first
|
||||
assert_kind_of Hash, entry
|
||||
assert_includes entry.keys, 'name'
|
||||
assert_equal 'inspec-test-fixture', entry['name']
|
||||
# TODO: any other fields to check? gem version?
|
||||
assert_equal 1, config_file.count, 'plugins.json should have one entry'
|
||||
assert config_file.existing_entry?(:'inspec-test-fixture')
|
||||
end
|
||||
|
||||
def test_install_a_gem_from_rubygems_org
|
||||
|
@ -196,11 +186,10 @@ class PluginInstallerInstallationTests < MiniTest::Test
|
|||
spec_path = File.join(@installer.gem_path, 'specifications', 'inspec-test-fixture-0.2.0.gemspec')
|
||||
refute File.exist?(spec_path), 'After pinned installation from rubygems.org, the wrong gemspec version should be absent'
|
||||
|
||||
plugin_json_path = File.join(ENV['INSPEC_CONFIG_DIR'], 'plugins.json')
|
||||
plugin_json_data = JSON.parse(File.read(plugin_json_path))
|
||||
entry = plugin_json_data['plugins'].detect { |e| e["name"] == 'inspec-test-fixture'}
|
||||
assert_includes entry.keys, 'version', 'plugins.json should include version pinning key'
|
||||
assert_equal '= 0.1.0', entry['version'], 'plugins.json should include version pinning value'
|
||||
config_file = Inspec::Plugin::V2::ConfigFile.new
|
||||
entry = config_file.plugin_by_name(:'inspec-test-fixture')
|
||||
assert_includes entry.keys, :version, 'plugins.json should include version pinning key'
|
||||
assert_equal '= 0.1.0', entry[:version], 'plugins.json should include version pinning value'
|
||||
end
|
||||
|
||||
def test_install_a_gem_with_conflicting_depends_from_rubygems_org
|
||||
|
@ -230,14 +219,13 @@ class PluginInstallerInstallationTests < MiniTest::Test
|
|||
specs = Dir.glob(File.join(@installer.gem_path, 'specifications', '*.gemspec'))
|
||||
assert_empty specs, 'After install-from-path, no gemspecs should be installed'
|
||||
|
||||
plugin_json_path = File.join(ENV['INSPEC_CONFIG_DIR'], 'plugins.json')
|
||||
plugin_json_data = JSON.parse(File.read(plugin_json_path))
|
||||
entry = plugin_json_data['plugins'].detect { |e| e["name"] == 'inspec-test-fixture'}
|
||||
assert_includes entry.keys, 'installation_type', 'plugins.json should include installation_type key'
|
||||
assert_equal 'path', entry['installation_type'], 'plugins.json should include path installation_type'
|
||||
config_file = Inspec::Plugin::V2::ConfigFile.new
|
||||
entry = config_file.plugin_by_name(:'inspec-test-fixture')
|
||||
assert_includes entry.keys, :installation_type, 'plugins.json should include installation_type key'
|
||||
assert_equal :path, entry[:installation_type], 'plugins.json should include path installation_type'
|
||||
|
||||
assert_includes entry.keys, 'installation_path', 'plugins.json should include installation_path key'
|
||||
assert_equal @plugin_fixture_src_path, entry['installation_path'], 'plugins.json should include correct value for installation path'
|
||||
assert_includes entry.keys, :installation_path, 'plugins.json should include installation_path key'
|
||||
assert_equal @plugin_fixture_src_path, entry[:installation_path], 'plugins.json should include correct value for installation path'
|
||||
end
|
||||
|
||||
def test_refuse_to_install_gem_whose_name_is_on_the_reject_list
|
||||
|
@ -335,11 +323,10 @@ class PluginInstallerUpdaterTests < MiniTest::Test
|
|||
assert File.exist?(spec_path), 'After update, the 0.1.0 gemspec should remain'
|
||||
|
||||
# Plugins file entry should be version pinned
|
||||
plugin_json_path = File.join(ENV['INSPEC_CONFIG_DIR'], 'plugins.json')
|
||||
plugin_json_data = JSON.parse(File.read(plugin_json_path))
|
||||
entry = plugin_json_data['plugins'].detect { |e| e["name"] == 'inspec-test-fixture'}
|
||||
assert_includes entry.keys, 'version', 'plugins.json should include version pinning key'
|
||||
assert_equal '= 0.2.0', entry['version'], 'plugins.json should include version pinning value'
|
||||
config_file = Inspec::Plugin::V2::ConfigFile.new
|
||||
entry = config_file.plugin_by_name(:'inspec-test-fixture')
|
||||
assert_includes entry.keys, :version, 'plugins.json should include version pinning key'
|
||||
assert_equal '= 0.2.0', entry[:version], 'plugins.json should include version pinning value'
|
||||
end
|
||||
|
||||
# TODO: Prevent updating a gem if it will lead to unsolveable dependencies
|
||||
|
@ -407,10 +394,8 @@ class PluginInstallerUninstallTests < MiniTest::Test
|
|||
assert_raises(Gem::UnsatisfiableDependencyError) { request_set.resolve(universe_set) }
|
||||
|
||||
# Plugins file entry should be removed
|
||||
plugin_json_path = File.join(ENV['INSPEC_CONFIG_DIR'], 'plugins.json')
|
||||
plugin_json_data = JSON.parse(File.read(plugin_json_path))
|
||||
entries = plugin_json_data['plugins'].select { |e| e["name"] == 'inspec-test-fixture'}
|
||||
assert_empty entries, "After gem-based uninstall, plugin name should be removed from plugins.json"
|
||||
config_file = Inspec::Plugin::V2::ConfigFile.new
|
||||
refute config_file.existing_entry?(:'inspec-test-fixture'), "After gem-based uninstall, plugin name should be removed from plugins.json"
|
||||
end
|
||||
|
||||
def test_uninstall_a_gem_plugin_removes_deps
|
||||
|
|
|
@ -101,16 +101,6 @@ class PluginLoaderTests < MiniTest::Test
|
|||
end
|
||||
end
|
||||
|
||||
def test_constuctor_when_the_plugin_config_is_corrupt_it_throws_an_exception
|
||||
ENV['INSPEC_CONFIG_DIR'] = File.join(@config_dir_path, 'corrupt')
|
||||
assert_raises(Inspec::Plugin::V2::ConfigError) { Inspec::Plugin::V2::Loader.new }
|
||||
end
|
||||
|
||||
def test_constuctor_when_the_plugin_config_is_a_bad_version_it_throws_an_exception
|
||||
ENV['INSPEC_CONFIG_DIR'] = File.join(@config_dir_path, 'bad_plugin_conf_version')
|
||||
assert_raises(Inspec::Plugin::V2::ConfigError) { Inspec::Plugin::V2::Loader.new }
|
||||
end
|
||||
|
||||
#====================================================================#
|
||||
# basic loading #
|
||||
#====================================================================#
|
||||
|
|
372
test/unit/plugin/v2/plugin_conf_test.rb
Normal file
372
test/unit/plugin/v2/plugin_conf_test.rb
Normal file
|
@ -0,0 +1,372 @@
|
|||
require 'minitest/spec'
|
||||
require 'minitest/autorun'
|
||||
require 'tmpdir'
|
||||
require_relative '../../../../lib/inspec/plugin/v2'
|
||||
|
||||
# This file relies on setting environment variables for some
|
||||
# of its tests - it is NOT thread-safe.
|
||||
|
||||
describe 'Inspec::Plugin::V2::ConfigFile' do
|
||||
orig_home = ENV['HOME']
|
||||
|
||||
let(:repo_path) { File.expand_path(File.join( __FILE__, '..', '..', '..', '..', '..')) }
|
||||
let(:config_fixtures_path) { File.join(repo_path, 'test', 'unit', 'mock', 'config_dirs') }
|
||||
let(:config_file_obj) { Inspec::Plugin::V2::ConfigFile.new(constructor_arg) }
|
||||
let(:constructor_arg) { File.join(config_fixtures_path, 'plugin_config_files', fixture_name + '.json') }
|
||||
|
||||
after do
|
||||
ENV['HOME'] = orig_home
|
||||
ENV['INSPEC_CONFIG_DIR'] = nil
|
||||
end
|
||||
|
||||
#----------------------------------------------------------#
|
||||
# Path Handling
|
||||
#----------------------------------------------------------#
|
||||
describe 'locating the file' do
|
||||
describe 'when no env var is set' do
|
||||
let(:constructor_arg) { nil }
|
||||
it 'defaults to the home directory' do
|
||||
ENV['HOME'] = File.join(config_fixtures_path, 'fakehome')
|
||||
expected_path = File.join(ENV['HOME'], '.inspec', 'plugins.json')
|
||||
|
||||
config_file_obj.path.must_equal expected_path
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when an env var is set' do
|
||||
let(:constructor_arg) { nil }
|
||||
it 'looks to the dir specified by the env var' do
|
||||
ENV['INSPEC_CONFIG_DIR'] = File.join(config_fixtures_path, 'meaning-by-path')
|
||||
expected_path = File.join(ENV['INSPEC_CONFIG_DIR'], 'plugins.json')
|
||||
|
||||
config_file_obj.path.must_equal expected_path
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when a path is provided to the constructor' do
|
||||
let(:fixture_name) { 'no_plugins' }
|
||||
it 'uses the provided path' do
|
||||
config_file_obj.path.must_equal constructor_arg
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#----------------------------------------------------------#
|
||||
# Reading a File
|
||||
#----------------------------------------------------------#
|
||||
|
||||
describe 'reading the file' do
|
||||
|
||||
describe 'when the file is missing' do
|
||||
let(:fixture_name) { 'nonesuch' }
|
||||
it 'creates a empty datastructure' do
|
||||
Dir.mktmpdir do |tmp_dir|
|
||||
constructor_arg = File.join(tmp_dir, 'plugins.json')
|
||||
config_file_obj.count.must_equal 0
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when the file is corrupt' do
|
||||
let(:fixture_name) { 'corrupt' }
|
||||
it 'throws an exception' do
|
||||
ex = assert_raises(Inspec::Plugin::V2::ConfigError) { config_file_obj }
|
||||
ex.message.must_include('Failed to load')
|
||||
ex.message.must_include('JSON')
|
||||
ex.message.must_include('unexpected token')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when the file is valid' do
|
||||
let(:fixture_name) { 'basic' }
|
||||
it 'can count plugins' do
|
||||
config_file_obj.count.must_equal 3
|
||||
end
|
||||
it 'can look up plugins by name with a String' do
|
||||
config_file_obj.plugin_by_name('inspec-test-fixture-01').wont_be_nil
|
||||
config_file_obj.plugin_by_name('inspec-test-fixture-99').must_be_nil
|
||||
end
|
||||
it 'can look up plugins by name with a Symbol' do
|
||||
config_file_obj.plugin_by_name(:'inspec-test-fixture-01').wont_be_nil
|
||||
config_file_obj.plugin_by_name(:'inspec-test-fixture-99').must_be_nil
|
||||
end
|
||||
it 'symbolizes the keys of the entries' do
|
||||
config_file_obj.each do |entry|
|
||||
entry.keys.each do |key|
|
||||
key.must_be_kind_of(Symbol)
|
||||
end
|
||||
end
|
||||
end
|
||||
it 'implements Enumerable' do
|
||||
config_file_obj.select { |entry| entry[:name].to_s.start_with?('inspec-test-fixture') }.count.must_equal 3
|
||||
end
|
||||
end
|
||||
|
||||
#----------------------------------------------------------#
|
||||
# Validation
|
||||
#----------------------------------------------------------#
|
||||
describe 'when the file is invalid' do
|
||||
|
||||
describe 'because the file version is wrong' do
|
||||
let(:fixture_name) { 'bad_plugin_conf_version' }
|
||||
it 'throws an exception' do
|
||||
ex = assert_raises(Inspec::Plugin::V2::ConfigError) { config_file_obj }
|
||||
ex.message.must_include('Unsupported')
|
||||
ex.message.must_include('version')
|
||||
ex.message.must_include('99.99.9')
|
||||
ex.message.must_include('1.0.0')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'because the file version is missing' do
|
||||
let(:fixture_name) { 'missing_plugin_conf_version' }
|
||||
it 'throws an exception' do
|
||||
ex = assert_raises(Inspec::Plugin::V2::ConfigError) { config_file_obj }
|
||||
ex.message.must_include('Missing')
|
||||
ex.message.must_include('version')
|
||||
ex.message.must_include('1.0.0')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'because the plugins field is missing' do
|
||||
let(:fixture_name) { 'missing_plugins_key' }
|
||||
it 'throws an exception' do
|
||||
ex = assert_raises(Inspec::Plugin::V2::ConfigError) { config_file_obj }
|
||||
ex.message.must_include('missing')
|
||||
ex.message.must_include("'plugins'")
|
||||
ex.message.must_include('array')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'because the plugins field is not an array' do
|
||||
let(:fixture_name) { 'hash_plugins_key' }
|
||||
it 'throws an exception' do
|
||||
ex = assert_raises(Inspec::Plugin::V2::ConfigError) { config_file_obj }
|
||||
ex.message.must_include('Malformed')
|
||||
ex.message.must_include("'plugins'")
|
||||
ex.message.must_include('array')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'because a plugin entry is not a hash' do
|
||||
let(:fixture_name) { 'entry_not_hash' }
|
||||
it 'throws an exception' do
|
||||
ex = assert_raises(Inspec::Plugin::V2::ConfigError) { config_file_obj }
|
||||
ex.message.must_include('Malformed')
|
||||
ex.message.must_include('Hash')
|
||||
ex.message.must_include('at index 2')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'because it contains duplicate plugin entries' do
|
||||
let(:fixture_name) { 'entry_duplicate' }
|
||||
it 'throws an exception' do
|
||||
ex = assert_raises(Inspec::Plugin::V2::ConfigError) { config_file_obj }
|
||||
ex.message.must_include('Malformed')
|
||||
ex.message.must_include('duplicate')
|
||||
ex.message.must_include('inspec-test-fixture-01')
|
||||
ex.message.must_include('at index 1 and 3')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'because a plugin entry does not have a name' do
|
||||
let(:fixture_name) { 'entry_no_name' }
|
||||
it 'throws an exception' do
|
||||
ex = assert_raises(Inspec::Plugin::V2::ConfigError) { config_file_obj }
|
||||
ex.message.must_include('Malformed')
|
||||
ex.message.must_include("missing 'name'")
|
||||
ex.message.must_include('at index 1')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'because a plugin entry has an unrecognized installation type' do
|
||||
let(:fixture_name) { 'entry_bad_installation_type' }
|
||||
it 'throws an exception' do
|
||||
ex = assert_raises(Inspec::Plugin::V2::ConfigError) { config_file_obj }
|
||||
ex.message.must_include('Malformed')
|
||||
ex.message.must_include('unrecognized installation_type')
|
||||
ex.message.must_include("one of 'gem' or 'path'")
|
||||
ex.message.must_include('at index 1')
|
||||
end
|
||||
end
|
||||
|
||||
describe 'because a path plugin entry does not have a path' do
|
||||
let(:fixture_name) { 'entry_no_path_for_path_type' }
|
||||
it 'throws an exception' do
|
||||
ex = assert_raises(Inspec::Plugin::V2::ConfigError) { config_file_obj }
|
||||
ex.message.must_include('Malformed')
|
||||
ex.message.must_include('missing installation path')
|
||||
ex.message.must_include('at index 2')
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'modifying the conf file' do
|
||||
#----------------------------------------------------------#
|
||||
# Adding Entries
|
||||
#----------------------------------------------------------#
|
||||
describe 'adding an entry' do
|
||||
let(:fixture_name) { 'no_plugins' }
|
||||
|
||||
describe 'when the conf is empty' do
|
||||
it 'should add one valid entry' do
|
||||
config_file_obj.count.must_equal 0
|
||||
config_file_obj.add_entry(name: 'inspec-test-fixture')
|
||||
config_file_obj.count.must_equal 1
|
||||
config_file_obj.plugin_by_name(:'inspec-test-fixture').wont_be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when the conf has entries' do
|
||||
let(:fixture_name) { 'basic' }
|
||||
it 'should append one valid entry' do
|
||||
config_file_obj.count.must_equal 3
|
||||
config_file_obj.add_entry(name: 'inspec-test-fixture-03')
|
||||
config_file_obj.count.must_equal 4
|
||||
config_file_obj.plugin_by_name(:'inspec-test-fixture-03').wont_be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when adding a gem entry' do
|
||||
it 'should add a gem entry' do
|
||||
config_file_obj.add_entry(
|
||||
name: 'inspec-test-fixture-03',
|
||||
installation_type: :gem,
|
||||
)
|
||||
entry = config_file_obj.plugin_by_name(:'inspec-test-fixture-03')
|
||||
entry.wont_be_nil
|
||||
entry[:installation_type].must_equal :gem
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when adding a path entry' do
|
||||
it 'should add a path entry' do
|
||||
config_file_obj.add_entry(
|
||||
name: 'inspec-test-fixture-03',
|
||||
installation_type: :path,
|
||||
installation_path: '/my/path.rb',
|
||||
)
|
||||
entry = config_file_obj.plugin_by_name(:'inspec-test-fixture-03')
|
||||
entry.wont_be_nil
|
||||
entry[:installation_type].must_equal :path
|
||||
entry[:installation_path].must_equal '/my/path.rb'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when adding a duplicate plugin name' do
|
||||
let(:fixture_name) { 'basic' }
|
||||
it 'should throw an exception' do
|
||||
assert_raises(Inspec::Plugin::V2::ConfigError) { config_file_obj.add_entry(name: 'inspec-test-fixture-02') }
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when adding an invalid entry' do
|
||||
it 'should throw an exception' do
|
||||
[
|
||||
{ name: 'inspec-test-fixture', installation_type: :path },
|
||||
{ installation_type: :gem },
|
||||
{ name: 'inspec-test-fixture', installation_type: :invalid },
|
||||
{ 'name' => 'inspec-test-fixture' },
|
||||
].each do |proposed_entry|
|
||||
assert_raises(Inspec::Plugin::V2::ConfigError) { config_file_obj.add_entry(proposed_entry) }
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
#----------------------------------------------------------#
|
||||
# Removing Entries
|
||||
#----------------------------------------------------------#
|
||||
describe 'removing an entry' do
|
||||
let(:fixture_name) { 'basic' }
|
||||
|
||||
describe 'when the entry exists' do
|
||||
it 'should remove the entry by symbol name' do
|
||||
config_file_obj.count.must_equal 3
|
||||
config_file_obj.plugin_by_name(:'inspec-test-fixture-01').wont_be_nil
|
||||
config_file_obj.remove_entry(:'inspec-test-fixture-01')
|
||||
config_file_obj.count.must_equal 2
|
||||
config_file_obj.plugin_by_name(:'inspec-test-fixture-01').must_be_nil
|
||||
end
|
||||
it 'should remove the entry by String name' do
|
||||
config_file_obj.count.must_equal 3
|
||||
config_file_obj.plugin_by_name('inspec-test-fixture-01').wont_be_nil
|
||||
config_file_obj.remove_entry('inspec-test-fixture-01')
|
||||
config_file_obj.count.must_equal 2
|
||||
config_file_obj.plugin_by_name('inspec-test-fixture-01').must_be_nil
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when the entry does not exist' do
|
||||
let(:fixture_name) { 'basic' }
|
||||
it 'should throw an exception' do
|
||||
config_file_obj.count.must_equal 3
|
||||
config_file_obj.plugin_by_name(:'inspec-test-fixture-99').must_be_nil
|
||||
ex = assert_raises(Inspec::Plugin::V2::ConfigError) { config_file_obj.remove_entry(:'inspec-test-fixture-99') }
|
||||
ex.message.must_include 'No such entry'
|
||||
ex.message.must_include 'inspec-test-fixture-99'
|
||||
config_file_obj.count.must_equal 3
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'writing the file' do
|
||||
let(:fixture_name) { 'unused' }
|
||||
|
||||
describe 'when the file does not exist' do
|
||||
it 'is created' do
|
||||
Dir.mktmpdir do |tmp_dir|
|
||||
path = File.join(tmp_dir, 'plugins.json')
|
||||
File.exist?(path).must_equal false
|
||||
cfo_writer = Inspec::Plugin::V2::ConfigFile.new(path)
|
||||
cfo_writer.add_entry(name: :'inspec-resource-lister')
|
||||
cfo_writer.save
|
||||
|
||||
File.exist?(path).must_equal true
|
||||
cfo_reader = Inspec::Plugin::V2::ConfigFile.new(path)
|
||||
cfo_reader.existing_entry?(:'inspec-resource-lister').must_equal true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when the directory does not exist' do
|
||||
it 'is created' do
|
||||
Dir.mktmpdir do |tmp_dir|
|
||||
path = File.join(tmp_dir, 'subdir', 'plugins.json')
|
||||
File.exist?(path).must_equal false
|
||||
cfo_writer = Inspec::Plugin::V2::ConfigFile.new(path)
|
||||
cfo_writer.add_entry(name: :'inspec-resource-lister')
|
||||
cfo_writer.save
|
||||
|
||||
File.exist?(path).must_equal true
|
||||
cfo_reader = Inspec::Plugin::V2::ConfigFile.new(path)
|
||||
cfo_reader.existing_entry?(:'inspec-resource-lister').must_equal true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when the file does exist' do
|
||||
it 'is overwritten' do
|
||||
Dir.mktmpdir do |tmp_dir|
|
||||
path = File.join(tmp_dir, 'plugins.json')
|
||||
cfo_writer = Inspec::Plugin::V2::ConfigFile.new(path)
|
||||
cfo_writer.add_entry(name: :'inspec-resource-lister')
|
||||
cfo_writer.save
|
||||
|
||||
File.exist?(path).must_equal true
|
||||
|
||||
cfo_modifier = Inspec::Plugin::V2::ConfigFile.new(path)
|
||||
cfo_modifier.remove_entry(:'inspec-resource-lister')
|
||||
cfo_modifier.add_entry(name: :'inspec-test-fixture')
|
||||
cfo_modifier.save
|
||||
|
||||
cfo_reader = Inspec::Plugin::V2::ConfigFile.new(path)
|
||||
cfo_reader.existing_entry?(:'inspec-resource-lister').must_equal false
|
||||
cfo_reader.existing_entry?(:'inspec-test-fixture').must_equal true
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Add table
Reference in a new issue