mirror of
https://github.com/inspec/inspec
synced 2024-11-10 07:04:15 +00:00
Use new config file system to read config
Signed-off-by: Clinton Wolfe <clintoncwolfe@gmail.com>
This commit is contained in:
parent
f1f5b27237
commit
102505a937
17 changed files with 298 additions and 218 deletions
|
@ -30,7 +30,7 @@ module Supermarket
|
|||
desc 'exec PROFILE', 'execute a Supermarket profile'
|
||||
exec_options
|
||||
def exec(*tests)
|
||||
o = opts(:exec).dup
|
||||
o = config
|
||||
diagnose(o)
|
||||
configure_logger(o)
|
||||
|
||||
|
|
|
@ -41,16 +41,16 @@ module Inspec
|
|||
# @param [Hash] config for the transport backend
|
||||
# @return [TransportBackend] enriched transport instance
|
||||
def self.create(config) # rubocop:disable Metrics/AbcSize
|
||||
conf = Train.target_config(config)
|
||||
name = Train.validate_backend(conf)
|
||||
transport = Train.create(name, conf)
|
||||
train_credentials = config.unpack_train_credentials
|
||||
transport_name = Train.validate_backend(train_credentials)
|
||||
transport = Train.create(transport_name, train_credentials)
|
||||
if transport.nil?
|
||||
raise "Can't find transport backend '#{name}'."
|
||||
raise "Can't find transport backend '#{transport_name}'."
|
||||
end
|
||||
|
||||
connection = transport.connection
|
||||
if connection.nil?
|
||||
raise "Can't connect to transport backend '#{name}'."
|
||||
raise "Can't connect to transport backend '#{transport_name}'."
|
||||
end
|
||||
|
||||
# Set caching settings. We always want to enable caching for
|
||||
|
@ -85,9 +85,9 @@ module Inspec
|
|||
|
||||
cls.new
|
||||
rescue Train::ClientError => e
|
||||
raise "Client error, can't connect to '#{name}' backend: #{e.message}"
|
||||
raise "Client error, can't connect to '#{transport_name}' backend: #{e.message}"
|
||||
rescue Train::TransportError => e
|
||||
raise "Transport error, can't connect to '#{name}' backend: #{e.message}"
|
||||
raise "Transport error, can't connect to '#{transport_name}' backend: #{e.message}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -75,8 +75,9 @@ module Inspec
|
|||
desc: 'Whether to use disable sspi authentication, defaults to false (WinRM).'
|
||||
option :winrm_basic_auth, type: :boolean,
|
||||
desc: 'Whether to use basic authentication, defaults to false (WinRM).'
|
||||
option :json_config, type: :string,
|
||||
option :config, type: :string,
|
||||
desc: 'Read configuration from JSON file (`-` reads from stdin).'
|
||||
option :json_config, type: :string, hide: true
|
||||
option :proxy_command, type: :string,
|
||||
desc: 'Specifies the command to use to connect to the server'
|
||||
option :bastion_host, type: :string,
|
||||
|
@ -118,92 +119,7 @@ module Inspec
|
|||
desc: 'Exit with code 101 if any tests fail, and 100 if any are skipped (default). If disabled, exit 0 on skips and 1 for failures.'
|
||||
end
|
||||
|
||||
def self.default_options
|
||||
{
|
||||
exec: {
|
||||
'reporter' => ['cli'],
|
||||
'show_progress' => false,
|
||||
'color' => true,
|
||||
'create_lockfile' => true,
|
||||
'backend_cache' => true,
|
||||
},
|
||||
shell: {
|
||||
'reporter' => ['cli'],
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
def self.parse_reporters(opts) # rubocop:disable Metrics/AbcSize
|
||||
# default to cli report for ad-hoc runners
|
||||
opts['reporter'] = ['cli'] if opts['reporter'].nil?
|
||||
|
||||
# parse out cli to proper report format
|
||||
if opts['reporter'].is_a?(Array)
|
||||
reports = {}
|
||||
opts['reporter'].each do |report|
|
||||
reporter_name, target = report.split(':', 2)
|
||||
if target.nil? || target.strip == '-'
|
||||
reports[reporter_name] = { 'stdout' => true }
|
||||
else
|
||||
reports[reporter_name] = {
|
||||
'file' => target,
|
||||
'stdout' => false,
|
||||
}
|
||||
reports[reporter_name]['target_id'] = opts['target_id'] if opts['target_id']
|
||||
end
|
||||
end
|
||||
opts['reporter'] = reports
|
||||
end
|
||||
|
||||
# add in stdout if not specified
|
||||
if opts['reporter'].is_a?(Hash)
|
||||
opts['reporter'].each do |reporter_name, config|
|
||||
opts['reporter'][reporter_name] = {} if config.nil?
|
||||
opts['reporter'][reporter_name]['stdout'] = true if opts['reporter'][reporter_name].empty?
|
||||
opts['reporter'][reporter_name]['target_id'] = opts['target_id'] if opts['target_id']
|
||||
end
|
||||
end
|
||||
|
||||
validate_reporters(opts['reporter'])
|
||||
opts
|
||||
end
|
||||
|
||||
def self.validate_reporters(reporters)
|
||||
return if reporters.nil?
|
||||
|
||||
valid_types = [
|
||||
'automate',
|
||||
'cli',
|
||||
'documentation',
|
||||
'html',
|
||||
'json',
|
||||
'json-automate',
|
||||
'json-min',
|
||||
'json-rspec',
|
||||
'junit',
|
||||
'progress',
|
||||
'yaml',
|
||||
]
|
||||
|
||||
reporters.each do |k, v|
|
||||
raise NotImplementedError, "'#{k}' is not a valid reporter type." unless valid_types.include?(k)
|
||||
|
||||
next unless k == 'automate'
|
||||
%w{token url}.each do |option|
|
||||
raise Inspec::ReporterError, "You must specify a automate #{option} via the json-config." if v[option].nil?
|
||||
end
|
||||
end
|
||||
|
||||
# check to make sure we are only reporting one type to stdout
|
||||
stdout = 0
|
||||
reporters.each_value do |v|
|
||||
stdout += 1 if v['stdout'] == true
|
||||
end
|
||||
|
||||
raise ArgumentError, 'The option --reporter can only have a single report outputting to stdout.' if stdout > 1
|
||||
end
|
||||
|
||||
def self.detect(params: {}, indent: 0, color: 39)
|
||||
def self.format_platform_info(params: {}, indent: 0, color: 39)
|
||||
str = ''
|
||||
params.each { |item, info|
|
||||
data = info
|
||||
|
@ -286,86 +202,12 @@ module Inspec
|
|||
false
|
||||
end
|
||||
|
||||
def diagnose(opts)
|
||||
return unless opts['diagnose']
|
||||
puts "InSpec version: #{Inspec::VERSION}"
|
||||
puts "Train version: #{Train::VERSION}"
|
||||
puts 'Command line configuration:'
|
||||
pp options
|
||||
puts 'JSON configuration file:'
|
||||
pp options_json
|
||||
puts 'Merged configuration:'
|
||||
pp opts
|
||||
puts
|
||||
def diagnose(_ = nil)
|
||||
config.diagnose
|
||||
end
|
||||
|
||||
def opts(type = nil)
|
||||
o = merged_opts(type)
|
||||
|
||||
# Due to limitations in Thor it is not possible to set an argument to be
|
||||
# both optional and its value to be mandatory. E.g. the user supplying
|
||||
# the --password argument is optional and not always required, but
|
||||
# whenever it is used, it requires a value. Handle options that were
|
||||
# defined above and require a value here:
|
||||
%w{password sudo-password}.each do |v|
|
||||
id = v.tr('-', '_').to_sym
|
||||
next unless o[id] == -1
|
||||
raise ArgumentError, "Please provide a value for --#{v}. For example: --#{v}=hello."
|
||||
end
|
||||
|
||||
# Infer `--sudo` if using `--sudo-password` without `--sudo`
|
||||
if o[:sudo_password] && !o[:sudo]
|
||||
o[:sudo] = true
|
||||
warn 'WARN: `--sudo-password` used without `--sudo`. Adding `--sudo`.'
|
||||
end
|
||||
|
||||
# check for compliance settings
|
||||
if o['compliance']
|
||||
require 'plugins/inspec-compliance/lib/inspec-compliance/api'
|
||||
InspecPlugins::Compliance::API.login(o['compliance'])
|
||||
end
|
||||
|
||||
o
|
||||
end
|
||||
|
||||
def merged_opts(type = nil)
|
||||
opts = {}
|
||||
|
||||
# start with default options if we have any
|
||||
opts = BaseCLI.default_options[type] unless type.nil? || BaseCLI.default_options[type].nil?
|
||||
opts['type'] = type unless type.nil?
|
||||
Inspec::BaseCLI.inspec_cli_command = type
|
||||
|
||||
# merge in any options from json-config
|
||||
json_config = options_json
|
||||
opts.merge!(json_config)
|
||||
|
||||
# merge in any options defined via thor
|
||||
opts.merge!(options)
|
||||
|
||||
# parse reporter options
|
||||
opts = BaseCLI.parse_reporters(opts) if %i(exec shell).include?(type)
|
||||
|
||||
Thor::CoreExt::HashWithIndifferentAccess.new(opts)
|
||||
end
|
||||
|
||||
def options_json
|
||||
conffile = options['json_config']
|
||||
@json ||= conffile ? read_config(conffile) : {}
|
||||
end
|
||||
|
||||
def read_config(file)
|
||||
if file == '-'
|
||||
puts 'WARN: reading JSON config from standard input' if STDIN.tty?
|
||||
config = STDIN.read
|
||||
else
|
||||
config = File.read(file)
|
||||
end
|
||||
|
||||
JSON.parse(config)
|
||||
rescue JSON::ParserError => e
|
||||
puts "Failed to load JSON configuration: #{e}\nConfig was: #{config.inspect}"
|
||||
exit 1
|
||||
def config
|
||||
@config ||= Inspec::Config.new(options) # 'options' here is CLI opts from Thor
|
||||
end
|
||||
|
||||
# get the log level
|
||||
|
@ -409,12 +251,12 @@ module Inspec
|
|||
|
||||
def configure_logger(o)
|
||||
#
|
||||
# TODO(ssd): This is a big gross, but this configures the
|
||||
# TODO(ssd): This is a bit gross, but this configures the
|
||||
# logging singleton Inspec::Log. Eventually it would be nice to
|
||||
# move internal debug logging to use this logging singleton.
|
||||
#
|
||||
loc = if o.log_location
|
||||
o.log_location
|
||||
loc = if o['log_location']
|
||||
o['log_location']
|
||||
elsif suppress_log_output?(o)
|
||||
STDERR
|
||||
else
|
||||
|
@ -422,14 +264,14 @@ module Inspec
|
|||
end
|
||||
|
||||
Inspec::Log.init(loc)
|
||||
Inspec::Log.level = get_log_level(o.log_level)
|
||||
Inspec::Log.level = get_log_level(o['log_level'])
|
||||
|
||||
o[:logger] = Logger.new(loc)
|
||||
# output json if we have activated the json formatter
|
||||
if o['log-format'] == 'json'
|
||||
o[:logger].formatter = Logger::JSONFormatter.new
|
||||
end
|
||||
o[:logger].level = get_log_level(o.log_level)
|
||||
o[:logger].level = get_log_level(o['log_level'])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -15,6 +15,7 @@ require 'inspec/plugin/v2'
|
|||
require 'inspec/runner_mock'
|
||||
require 'inspec/env_printer'
|
||||
require 'inspec/schema'
|
||||
require 'inspec/config'
|
||||
|
||||
class Inspec::InspecCLI < Inspec::BaseCLI
|
||||
class_option :log_level, aliases: :l, type: :string,
|
||||
|
@ -45,12 +46,12 @@ class Inspec::InspecCLI < Inspec::BaseCLI
|
|||
desc: 'A list of controls to include. Ignore all other tests.'
|
||||
profile_options
|
||||
def json(target)
|
||||
o = opts.dup
|
||||
o = config
|
||||
diagnose(o)
|
||||
o['log_location'] = STDERR
|
||||
configure_logger(o)
|
||||
|
||||
o[:backend] = Inspec::Backend.create(target: 'mock://')
|
||||
o[:backend] = Inspec::Backend.create(Inspec::Config.mock)
|
||||
o[:check_mode] = true
|
||||
o[:vendor_cache] = Inspec::Cache.new(o[:vendor_cache])
|
||||
|
||||
|
@ -81,9 +82,9 @@ class Inspec::InspecCLI < Inspec::BaseCLI
|
|||
option :format, type: :string
|
||||
profile_options
|
||||
def check(path) # rubocop:disable Metrics/AbcSize
|
||||
o = opts.dup
|
||||
o = config
|
||||
diagnose(o)
|
||||
o[:backend] = Inspec::Backend.create(target: 'mock://')
|
||||
o[:backend] = Inspec::Backend.create(Inspec::Config.mock)
|
||||
o[:check_mode] = true
|
||||
o[:vendor_cache] = Inspec::Cache.new(o[:vendor_cache])
|
||||
|
||||
|
@ -133,10 +134,10 @@ class Inspec::InspecCLI < Inspec::BaseCLI
|
|||
option :overwrite, type: :boolean, default: false,
|
||||
desc: 'Overwrite existing vendored dependencies and lockfile.'
|
||||
def vendor(path = nil)
|
||||
o = opts.dup
|
||||
o = config
|
||||
configure_logger(o)
|
||||
o[:logger] = Logger.new(STDOUT)
|
||||
o[:logger].level = get_log_level(o.log_level)
|
||||
o[:logger].level = get_log_level(o[:log_level])
|
||||
|
||||
vendor_deps(path, o)
|
||||
end
|
||||
|
@ -154,12 +155,12 @@ class Inspec::InspecCLI < Inspec::BaseCLI
|
|||
option :ignore_errors, type: :boolean, default: false,
|
||||
desc: 'Ignore profile warnings.'
|
||||
def archive(path)
|
||||
o = opts.dup
|
||||
o = config
|
||||
diagnose(o)
|
||||
|
||||
o[:logger] = Logger.new(STDOUT)
|
||||
o[:logger].level = get_log_level(o.log_level)
|
||||
o[:backend] = Inspec::Backend.create(target: 'mock://')
|
||||
o[:logger].level = get_log_level(o[:log_level])
|
||||
o[:backend] = Inspec::Backend.create(Inspec::Config.mock)
|
||||
|
||||
# Force vendoring with overwrite when archiving
|
||||
vendor_options = o.dup
|
||||
|
@ -254,7 +255,7 @@ class Inspec::InspecCLI < Inspec::BaseCLI
|
|||
EOT
|
||||
exec_options
|
||||
def exec(*targets)
|
||||
o = opts(:exec).dup
|
||||
o = config
|
||||
diagnose(o)
|
||||
configure_logger(o)
|
||||
|
||||
|
@ -273,14 +274,14 @@ class Inspec::InspecCLI < Inspec::BaseCLI
|
|||
target_options
|
||||
option :format, type: :string
|
||||
def detect
|
||||
o = opts(:detect).dup
|
||||
o = config
|
||||
o[:command] = 'platform.params'
|
||||
(_, res) = run_command(o)
|
||||
if o['format'] == 'json'
|
||||
puts res.to_json
|
||||
else
|
||||
headline('Platform Details')
|
||||
puts Inspec::BaseCLI.detect(params: res, indent: 0, color: 36)
|
||||
puts Inspec::BaseCLI.format_platform_info(params: res, indent: 0, color: 36)
|
||||
end
|
||||
rescue ArgumentError, RuntimeError, Train::UserError => e
|
||||
$stderr.puts e.message
|
||||
|
@ -301,13 +302,13 @@ class Inspec::InspecCLI < Inspec::BaseCLI
|
|||
option :distinct_exit, type: :boolean, default: true,
|
||||
desc: 'Exit with code 100 if any tests fail, and 101 if any are skipped but none failed (default). If disabled, exit 0 on skips and 1 for failures.'
|
||||
def shell_func
|
||||
o = opts(:shell).dup
|
||||
o = config
|
||||
diagnose(o)
|
||||
o[:debug_shell] = true
|
||||
|
||||
log_device = suppress_log_output?(o) ? nil : STDOUT
|
||||
o[:logger] = Logger.new(log_device)
|
||||
o[:logger].level = get_log_level(o.log_level)
|
||||
o[:logger].level = get_log_level(o[:log_level])
|
||||
|
||||
if o[:command].nil?
|
||||
runner = Inspec::Runner.new(o)
|
||||
|
@ -346,7 +347,7 @@ class Inspec::InspecCLI < Inspec::BaseCLI
|
|||
desc 'version', 'prints the version of this tool'
|
||||
option :format, type: :string
|
||||
def version
|
||||
if opts['format'] == 'json'
|
||||
if config['format'] == 'json'
|
||||
v = { version: Inspec::VERSION }
|
||||
puts v.to_json
|
||||
else
|
||||
|
@ -363,7 +364,7 @@ class Inspec::InspecCLI < Inspec::BaseCLI
|
|||
private
|
||||
|
||||
def run_command(opts)
|
||||
runner = Inspec::Runner.new(opts)
|
||||
runner = Inspec::Runner.new(Inspec::Config.new(opts))
|
||||
res = runner.eval_with_virtual_profile(opts[:command])
|
||||
runner.load
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
# and CLI arguments.
|
||||
|
||||
require 'pp'
|
||||
require 'stringio'
|
||||
|
||||
module Inspec
|
||||
class Config
|
||||
|
@ -23,6 +24,11 @@ module Inspec
|
|||
def_delegators :@final_options, :each, :delete, :[], :[]=, :key?
|
||||
attr_reader :final_options
|
||||
|
||||
# This makes it easy to make a config with a mock backend.
|
||||
def self.mock
|
||||
Inspec::Config.new({ backend: :mock }, StringIO.new('{}'))
|
||||
end
|
||||
|
||||
def initialize(cli_opts = {}, cfg_io = nil, command_name = nil)
|
||||
@command_name = command_name || (ARGV.empty? ? nil : ARGV[0].to_sym)
|
||||
@defaults = Defaults.for_command(@command_name)
|
||||
|
@ -48,6 +54,89 @@ module Inspec
|
|||
puts
|
||||
end
|
||||
|
||||
#-----------------------------------------------------------------------#
|
||||
# Train Credential Handling
|
||||
#-----------------------------------------------------------------------#
|
||||
|
||||
# Returns a Hash with Symbol keys as follows:
|
||||
# backend: machine name of the Train transport needed
|
||||
# If present, any of the GENERIC_CREDENTIALS.
|
||||
# All other keys are specific to the backend.
|
||||
#
|
||||
# The credentials are gleaned from:
|
||||
# * the Train transport defaults. Train handles this on transport creation,
|
||||
# so this method doesn't load defaults.
|
||||
# * individual InSpec CLI options (which in many cases may have the
|
||||
# transport name prefixed, which is stripped before being added
|
||||
# to the creds hash)
|
||||
# * the --target CLI option, which is interpreted:
|
||||
# - as an arbitrary URI, which is parsed by Train.unpack_target_from_uri
|
||||
|
||||
def unpack_train_credentials
|
||||
# Internally, use indifferent access while we build the creds
|
||||
credentials = Thor::CoreExt::HashWithIndifferentAccess.new({})
|
||||
|
||||
# Helper methods prefixed with _utc_ (Unpack Train Credentials)
|
||||
|
||||
credentials.merge!(_utc_generic_credentials)
|
||||
|
||||
_utc_determine_backend(credentials)
|
||||
# credentials.merge!(Train.unpack_target_from_uri(final_options[:target])) # TODO: this will be replaced with the credset work
|
||||
credentials.merge!(Train.target_config(final_options)) # TODO: this will be replaced with the credset work
|
||||
transport_name = credentials[:backend].to_s
|
||||
_utc_merge_transport_options(credentials, transport_name)
|
||||
|
||||
# Convert to all-Symbol keys
|
||||
credentials.each_with_object({}) do |(option, value), creds|
|
||||
creds[option.to_sym] = value
|
||||
creds
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def _utc_merge_transport_options(credentials, transport_name)
|
||||
# Ask Train for the names of the transport options
|
||||
transport_options = Train.options(transport_name).keys.map(&:to_s)
|
||||
|
||||
# If there are any options with those (unprefixed) names, merge them in.
|
||||
unprefixed_transport_options = final_options.select do |option_name, _value|
|
||||
transport_options.include? option_name # e.g., 'host'
|
||||
end
|
||||
credentials.merge!(unprefixed_transport_options)
|
||||
|
||||
# If there are any prefixed options, merge them in, stripping the prefix.
|
||||
transport_prefix = transport_name.downcase.tr('-', '_') + '_'
|
||||
transport_options.each do |bare_option_name|
|
||||
prefixed_option_name = transport_prefix + bare_option_name.to_s
|
||||
if final_options.key?(prefixed_option_name)
|
||||
credentials[bare_option_name.to_s] = final_options[prefixed_option_name]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# fetch any info that applies to all transports (like sudo information)
|
||||
def _utc_generic_credentials
|
||||
@final_options.select { |option, _value| GENERIC_CREDENTIALS.include?(option) }
|
||||
end
|
||||
|
||||
def _utc_determine_backend(credentials)
|
||||
return if credentials.key?(:backend)
|
||||
|
||||
# Default to local
|
||||
unless @final_options.key?(:target)
|
||||
credentials[:backend] = 'local'
|
||||
return
|
||||
end
|
||||
|
||||
# Look into target
|
||||
%r{^(?<transport_name>[a-z_\-0-9]+)://.*$} =~ final_options[:target]
|
||||
unless transport_name
|
||||
raise ArgumentError, "Could not recognize a backend from the target #{final_options[:target]} - use a URI format with the backend name as the URI schema. Example: 'ssh://somehost.com' or 'transport://credset' or 'transport://' if credentials are provided outside of InSpec."
|
||||
end
|
||||
credentials[:backend] = transport_name.to_s # these are indeed stored in Train as Strings.
|
||||
end
|
||||
|
||||
#-----------------------------------------------------------------------#
|
||||
# Reading Config Files
|
||||
#-----------------------------------------------------------------------#
|
||||
|
|
|
@ -78,7 +78,7 @@ module Inspec::DSL
|
|||
end
|
||||
|
||||
def self.filter_included_controls(context, profile, &block)
|
||||
mock = Inspec::Backend.create({ backend: 'mock' })
|
||||
mock = Inspec::Backend.create(Inspec::Config.mock)
|
||||
include_ctx = Inspec::ProfileContext.for_profile(profile, mock, {})
|
||||
include_ctx.load(block) if block_given?
|
||||
# remove all rules that were not registered
|
||||
|
|
|
@ -67,7 +67,8 @@ module Inspec
|
|||
new(reader, opts)
|
||||
end
|
||||
|
||||
def self.for_fetcher(fetcher, opts)
|
||||
def self.for_fetcher(fetcher, config)
|
||||
opts = config.respond_to?(:final_options) ? config.final_options : config
|
||||
opts[:vendor_cache] = opts[:vendor_cache] || Cache.new
|
||||
path, writable = fetcher.fetch
|
||||
for_path(path, opts.merge(target: fetcher.target, writable: writable))
|
||||
|
@ -113,7 +114,7 @@ module Inspec
|
|||
#
|
||||
# This will cause issues if a profile attempts to load a file via `inspec.profile.file`
|
||||
train_options = options.reject { |k, _| k == 'target' } # See https://github.com/chef/inspec/pull/1646
|
||||
@backend = options[:backend].nil? ? Inspec::Backend.create(train_options) : options[:backend].dup
|
||||
@backend = options[:backend].nil? ? Inspec::Backend.create(Inspec::Config.new(train_options)) : options[:backend].dup
|
||||
@runtime_profile = RuntimeProfile.new(self)
|
||||
@backend.profile = @runtime_profile
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
# author: Adam Leff
|
||||
|
||||
require 'inspec/profile'
|
||||
require 'inspec/config'
|
||||
|
||||
module Inspec
|
||||
class ProfileVendor
|
||||
|
@ -49,7 +50,7 @@ module Inspec
|
|||
def profile_opts
|
||||
{
|
||||
vendor_cache: Inspec::Cache.new(cache_path.to_s),
|
||||
backend: Inspec::Backend.create(target: 'mock://'),
|
||||
backend: Inspec::Backend.create(Inspec::Config.mock),
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@ require 'inspec/profile_context'
|
|||
require 'inspec/profile'
|
||||
require 'inspec/metadata'
|
||||
require 'inspec/secrets'
|
||||
require 'inspec/config'
|
||||
require 'inspec/dependencies/cache'
|
||||
# spec requirements
|
||||
|
||||
|
@ -34,7 +35,10 @@ module Inspec
|
|||
attr_reader :backend, :rules, :attributes
|
||||
def initialize(conf = {})
|
||||
@rules = []
|
||||
@conf = conf.dup
|
||||
# If we were handed a Hash config (by audit cookbook or kitchen-inspec),
|
||||
# upgrade it to a proper config. This handles a lot of config finalization,
|
||||
# like reporter parsing.
|
||||
@conf = conf.is_a?(Hash) ? Inspec::Config.new(conf) : conf
|
||||
@conf[:logger] ||= Logger.new(nil)
|
||||
@target_profiles = []
|
||||
@controls = @conf[:controls] || []
|
||||
|
@ -42,10 +46,6 @@ module Inspec
|
|||
@create_lockfile = @conf[:create_lockfile]
|
||||
@cache = Inspec::Cache.new(@conf[:vendor_cache])
|
||||
|
||||
# parse any ad-hoc runners reporter formats
|
||||
# this has to happen before we load the test_collector
|
||||
@conf = Inspec::BaseCLI.parse_reporters(@conf) if @conf[:type].nil?
|
||||
|
||||
@test_collector = @conf.delete(:test_collector) || begin
|
||||
require 'inspec/runner_rspec'
|
||||
RunnerRspec.new(@conf)
|
||||
|
|
|
@ -104,7 +104,7 @@ module Inspec
|
|||
puts <<~EOF
|
||||
You are currently running on:
|
||||
|
||||
#{Inspec::BaseCLI.detect(params: ctx.platform.params, indent: 4, color: 39)}
|
||||
#{Inspec::BaseCLI.format_platform_info(params: ctx.platform.params, indent: 4, color: 39)}
|
||||
EOF
|
||||
end
|
||||
|
||||
|
|
|
@ -49,23 +49,11 @@ describe 'inspec archive' do
|
|||
end
|
||||
end
|
||||
|
||||
it 'archive wont overwrite existing files' do
|
||||
it 'archive will overwrite existing files even without --overwrite' do
|
||||
prepare_examples('profile') do |dir|
|
||||
x = rand.to_s
|
||||
File.write(dst.path, x)
|
||||
out = inspec('archive ' + dir + ' --output ' + dst.path)
|
||||
out.stderr.must_equal '' # uh...
|
||||
out.stdout.must_include "Archive #{dst.path} exists already. Use --overwrite."
|
||||
out.exit_status.must_equal 1
|
||||
File.read(dst.path).must_equal x
|
||||
end
|
||||
end
|
||||
|
||||
it 'archive will overwrite files if necessary' do
|
||||
prepare_examples('profile') do |dir|
|
||||
x = rand.to_s
|
||||
File.write(dst.path, x)
|
||||
out = inspec('archive ' + dir + ' --output ' + dst.path + ' --overwrite')
|
||||
out.stderr.must_equal ''
|
||||
out.stdout.must_include 'Generate archive ' + dst.path
|
||||
out.exit_status.must_equal 0
|
||||
|
|
|
@ -6,6 +6,7 @@ require 'functional/helper'
|
|||
|
||||
describe 'inspec exec' do
|
||||
include FunctionalHelper
|
||||
let(:looks_like_a_stacktrace) { %r{lib/inspec/.+\.rb:\d+:in} }
|
||||
|
||||
it 'can execute the profile' do
|
||||
out = inspec('exec ' + example_profile + ' --no-create-lockfile')
|
||||
|
@ -377,6 +378,7 @@ Test Summary: \e[38;5;41m2 successful\e[0m, 0 failures, 0 skipped\n"
|
|||
str = "Sudo is only valid when running against a remote host. To run this locally with elevated privileges, run the command with `sudo ...`.\n"
|
||||
out.stderr.force_encoding(Encoding::UTF_8).must_include str
|
||||
out.exit_status.must_equal 1
|
||||
# TODO: check for stacktrace
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -555,4 +557,142 @@ Test Summary: \e[38;5;41m2 successful\e[0m, 0 failures, 0 skipped\n"
|
|||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when specifying a config file' do
|
||||
let(:run_result) { run_inspec_process('exec ' + File.join(profile_path, 'simple-metadata') + ' ' + cli_args, json: true, env: env)}
|
||||
let(:seen_target_id) { run_result.payload.json['platform']['target_id'] }
|
||||
let(:stderr) { run_result.stderr }
|
||||
let(:env) { {} }
|
||||
|
||||
describe 'when using the legacy --json-config option' do
|
||||
let(:cli_args) { '--json-config ' + File.join(config_dir_path, 'json-config', 'good.json') }
|
||||
it 'should see the custom target ID value' do
|
||||
stderr.must_be_empty # TODO: one day deprecate the --json-config option
|
||||
seen_target_id.must_equal 'from-config-file'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when using the --config option to read from a custom file' do
|
||||
let(:cli_args) { '--config ' + File.join(config_dir_path, 'json-config', 'good.json') }
|
||||
it 'should see the custom target ID value' do
|
||||
stderr.must_be_empty
|
||||
seen_target_id.must_equal 'from-config-file'
|
||||
end
|
||||
end
|
||||
|
||||
# TODO - Use bash redirection to populate STDIN
|
||||
# unless windows?
|
||||
# describe 'when using the --config option to read from STDIN' do
|
||||
# let(:cli_args) { '--config ' + File.join(config_dir_path, 'json-config', 'good.json') }
|
||||
# it 'should see the custom target ID value' do
|
||||
# stderr.must_be_empty
|
||||
# seen_target_id.must_equal 'from-config-file'
|
||||
# # TODO
|
||||
# end
|
||||
# end
|
||||
# end
|
||||
|
||||
describe 'when reading from the default location' do
|
||||
# Should read from File.join(config_dir_path, 'fakehome-2', '.inspec', 'config.json')
|
||||
let(:env) { { 'HOME' => File.join(config_dir_path, 'fakehome-2') } }
|
||||
let(:cli_args) { '' }
|
||||
it 'should see the homedir target ID value' do
|
||||
stderr.must_be_empty
|
||||
seen_target_id.must_equal 'from-fakehome-config-file'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when --config points to a nonexistant location' do
|
||||
let(:cli_args) { '--config ' + 'no/such/path' }
|
||||
it 'should issue an error with the file path' do
|
||||
stderr.wont_match looks_like_a_stacktrace
|
||||
run_result.exit_status.must_equal 1
|
||||
stderr.must_include 'Could not read configuration file' # Should specify error
|
||||
stderr.must_include 'no/such/path' # Should include error value seen
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when --config points to a malformed file' do
|
||||
let(:cli_args) { '--config ' + File.join(config_dir_path, 'json-config', 'malformed.json') }
|
||||
it 'should issue an error with the parse message' do
|
||||
stderr.wont_match looks_like_a_stacktrace
|
||||
run_result.exit_status.must_equal 1
|
||||
stderr.must_include 'Failed to load JSON'
|
||||
stderr.must_include 'Config was:'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when --config points to an invalid file' do
|
||||
let(:cli_args) { '--config ' + File.join(config_dir_path, 'json-config', 'invalid.json') }
|
||||
it 'should issue an error with the parse message' do
|
||||
stderr.wont_match looks_like_a_stacktrace
|
||||
run_result.exit_status.must_equal 1
|
||||
stderr.must_include 'Unrecognized top-level configuration'
|
||||
stderr.must_include 'this_key_is_invalid'
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when specifying the execution target' do
|
||||
let(:local_plat) do
|
||||
json = run_inspec_process('detect --format json', {}).stdout
|
||||
# .slice is available in ruby 2.5+
|
||||
JSON.parse(json).select{|k,v| ['name', 'release'].include? k }
|
||||
end
|
||||
let(:run_result) { run_inspec_process('exec ' + File.join(profile_path, 'simple-metadata') + ' ' + cli_args, json: true) }
|
||||
let(:seen_platform) { run_result.payload.json['platform'].select{|k,v| ['name', 'release'].include? k } }
|
||||
let(:stderr) { run_result.stderr }
|
||||
|
||||
describe 'when neither target nor backend is specified' do
|
||||
let(:cli_args) { '' }
|
||||
it 'should connect to the local platform' do
|
||||
seen_platform.must_equal local_plat
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when local:// is specified' do
|
||||
let(:cli_args) { ' -t local:// ' }
|
||||
it 'should connect to the local platform' do
|
||||
seen_platform.must_equal local_plat
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when an unrecognized backend is specified' do
|
||||
let(:cli_args) { '-b garble ' }
|
||||
it 'should exit with an error' do
|
||||
run_result.exit_status.must_equal 1
|
||||
stderr.wont_match looks_like_a_stacktrace
|
||||
# "Can't find train plugin garble. Please install it first"
|
||||
stderr.must_include 'Can\'t find train plugin'
|
||||
stderr.must_include 'garble'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when an unrecognized target schema is specified' do
|
||||
let(:cli_args) { '-t garble:// ' }
|
||||
it 'should exit with an error' do
|
||||
run_result.exit_status.must_equal 1
|
||||
stderr.wont_match looks_like_a_stacktrace
|
||||
# "Can't find train plugin garble. Please install it first"
|
||||
stderr.must_include 'Can\'t find train plugin'
|
||||
stderr.must_include 'garble'
|
||||
end
|
||||
end
|
||||
|
||||
describe 'when a schemaless URI is specified' do
|
||||
let(:cli_args) { '-t garble ' }
|
||||
it 'should exit with an error' do
|
||||
run_result.exit_status.must_equal 1
|
||||
stderr.wont_match looks_like_a_stacktrace
|
||||
# "Could not recognize a backend from the target garble - use a URI
|
||||
# format with the backend name as the URI schema. Example: 'ssh://somehost.com'
|
||||
# or 'transport://credset' or 'transport://' if credentials are provided
|
||||
# outside of InSpec."
|
||||
stderr.must_include 'Could not recognize a backend'
|
||||
stderr.must_include 'garble'
|
||||
stderr.must_include 'ssh://somehost.com'
|
||||
stderr.must_include 'transport://credset'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -34,6 +34,7 @@ require 'inspec/runner'
|
|||
require 'inspec/runner_mock'
|
||||
require 'inspec/globals'
|
||||
require 'inspec/impact'
|
||||
require 'inspec/config'
|
||||
require 'fetchers/mock'
|
||||
require 'inspec/dependencies/cache'
|
||||
|
||||
|
@ -89,7 +90,7 @@ class MockLoader
|
|||
scriptpath = ::File.realpath(::File.dirname(__FILE__))
|
||||
|
||||
# create mock backend
|
||||
@backend = Inspec::Backend.create({ backend: :mock, verbose: true })
|
||||
@backend = Inspec::Backend.create(Inspec::Config.mock)
|
||||
mock = @backend.backend
|
||||
|
||||
# create all mock files
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"version": "1.1",
|
||||
"cli_options": {
|
||||
"target_id": "from-fakehome-config-file"
|
||||
}
|
||||
}
|
6
test/unit/mock/config_dirs/json-config/good.json
Normal file
6
test/unit/mock/config_dirs/json-config/good.json
Normal file
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"version": "1.1",
|
||||
"cli_options": {
|
||||
"target_id": "from-config-file"
|
||||
}
|
||||
}
|
4
test/unit/mock/config_dirs/json-config/invalid.json
Normal file
4
test/unit/mock/config_dirs/json-config/invalid.json
Normal file
|
@ -0,0 +1,4 @@
|
|||
{
|
||||
"version": "1.1",
|
||||
"this_key_is_invalid": "yes"
|
||||
}
|
1
test/unit/mock/config_dirs/json-config/malformed.json
Normal file
1
test/unit/mock/config_dirs/json-config/malformed.json
Normal file
|
@ -0,0 +1 @@
|
|||
[asd9f78oihjasdfljkn
|
Loading…
Reference in a new issue