Merge pull request #3935 from inspec/cw/add-license-gating

Add license acceptance to InSpec v4
This commit is contained in:
Clinton Wolfe 2019-04-11 18:56:11 -04:00 committed by GitHub
commit 4348e8fa07
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 187 additions and 13 deletions

View file

@ -8,6 +8,7 @@ require 'passgen'
require 'train'
require_relative 'tasks/maintainers'
require_relative 'tasks/spdx'
require 'fileutils'
Bundler::GemHelper.install_tasks name: 'inspec'
@ -70,16 +71,38 @@ namespace :test do
end or fail 'Failures'
end
task :accept_license do
FileUtils.mkdir_p(File.join(Dir.home, '.chef', 'accepted_licenses'))
# If the user has not accepted the license, touch the acceptance
# file, but also touch a marker that it is only for testing.
unless File.exist?(File.join(Dir.home, '.chef', 'accepted_licenses', 'inspec'))
puts "\n\nTemporarily accepting Chef user license for the duration of testing...\n"
FileUtils.touch(File.join(Dir.home, '.chef', 'accepted_licenses', 'inspec'))
FileUtils.touch(File.join(Dir.home, '.chef', 'accepted_licenses', 'inspec.for_testing'))
end
# Regardless of what happens, when this process exits, check for cleanup.
at_exit do
if File.exist?(File.join(Dir.home, '.chef', 'accepted_licenses', 'inspec.for_testing'))
puts "\n\nRemoving temporary Chef user license acceptance file that was placed for test duration.\n"
FileUtils.rm_f(File.join(Dir.home, '.chef', 'accepted_licenses', 'inspec'))
FileUtils.rm_f(File.join(Dir.home, '.chef', 'accepted_licenses', 'inspec.for_testing'))
end
end
end
Rake::TestTask.new(:functional) do |t|
t.libs << 'test'
t.test_files = Dir.glob([
'test/functional/**/*_test.rb',
'lib/plugins/inspec-*/test/functional/**/*_test.rb',
])
t.warning = false
t.warning = false # This just complains about things in underlying libraries
t.verbose = true
t.ruby_opts = ['--dev'] if defined?(JRUBY_VERSION)
end
# Inject a prerequisite task
task :functional => [:accept_license]
# Functional tests on Windows take a bit to run. This
# optionally takes a env to breake the tests up into 3 workers.
@ -93,10 +116,12 @@ namespace :test do
t.libs << 'test'
t.test_files = files
t.warning = false
t.warning = false # This just complains about things in underlying libraries
t.verbose = true
t.ruby_opts = ['--dev'] if defined?(JRUBY_VERSION)
end
# Inject a prerequisite task
task :'functional:windows' => [:accept_license]
task :resources do
tests = Dir['test/resource/*_test.rb']
@ -122,6 +147,8 @@ namespace :test do
FileUtils.rm(destination)
end
end
# Inject a prerequisite task
task :'integration' => [:accept_license]
task :ssh, [:target] do |_t, args|
tests_path = File.join(File.dirname(__FILE__), 'test', 'integration', 'test', 'integration', 'default')

View file

@ -21,6 +21,7 @@ Gem::Specification.new do |spec|
spec.required_ruby_version = '>= 2.3'
spec.add_dependency 'train-core', '~> 2.0'
spec.add_dependency 'license-acceptance', '~> 0.2'
spec.add_dependency 'thor', '~> 0.20'
spec.add_dependency 'json', '>= 1.8', '< 3.0'
spec.add_dependency 'method_source', '~> 0.8'

View file

@ -31,6 +31,7 @@ Gem::Specification.new do |spec|
spec.add_dependency 'train-aws', '~> 0.1'
# Implementation dependencies
spec.add_dependency 'license-acceptance', '~> 0.2'
spec.add_dependency 'thor', '~> 0.20'
spec.add_dependency 'json', '>= 1.8', '< 3.0'
spec.add_dependency 'method_source', '~> 0.8'

View file

@ -39,6 +39,9 @@ class Inspec::InspecCLI < Inspec::BaseCLI
class_option :disable_user_plugins, type: :string, banner: '',
desc: 'Disable loading all plugins that the user installed.'
require 'license_acceptance/cli_flags/thor'
include LicenseAcceptance::CLIFlags::Thor
desc 'json PATH', 'read all tests in PATH and generate a JSON summary'
option :output, aliases: :o, type: :string,
desc: 'Save the created profile to a path'
@ -196,6 +199,7 @@ class Inspec::InspecCLI < Inspec::BaseCLI
3 Fatal deprecation encountered
100 Normal exit, at least one test failed
101 Normal exit, at least one test skipped but none failed
172 Chef License not accepted
```
Below are some examples of using `exec` with different test LOCATIONS:
@ -373,18 +377,44 @@ class Inspec::InspecCLI < Inspec::BaseCLI
end
end
begin
# Handle help commands
# This allows you to use any of the normal help commands after the normal args.
help_commands = ['-h', '--help', 'help']
(help_commands & ARGV).each do |cmd|
# move the help argument to one place behind the end for Thor to digest
if ARGV.size > 1
match = ARGV.delete(cmd)
ARGV.insert(-2, match)
end
end
#=====================================================================#
# Pre-Flight Code
#=====================================================================#
help_commands = ['-h', '--help', 'help']
version_commands = ['-v', '--version', 'version']
commands_exempt_from_license_check = help_commands + version_commands
#---------------------------------------------------------------------#
# EULA acceptance
#---------------------------------------------------------------------#
require 'license_acceptance/acceptor'
begin
if (commands_exempt_from_license_check & ARGV.map(&:downcase)).empty? && # Did they use a non-exempt command?
!ARGV.empty? # Did they supply at least one command?
LicenseAcceptance::Acceptor.check_and_persist('inspec', Inspec::VERSION)
end
rescue LicenseAcceptance::LicenseNotAcceptedError
Inspec::Log.error 'InSpec cannot execute without accepting the license'
Inspec::UI.new.exit(:license_not_accepted)
end
#---------------------------------------------------------------------#
# Adjustments for help handling
# This allows you to use any of the normal help commands after the normal args.
#---------------------------------------------------------------------#
(help_commands & ARGV).each do |cmd|
# move the help argument to one place behind the end for Thor to digest
if ARGV.size > 1
match = ARGV.delete(cmd)
ARGV.insert(-2, match)
end
end
#---------------------------------------------------------------------#
# Plugin Loading
#---------------------------------------------------------------------#
begin
# Load v2 plugins. Manually check for plugin disablement.
omit_core = ARGV.delete('--disable-core-plugins')
omit_user = ARGV.delete('--disable-user-plugins')

View file

@ -33,6 +33,7 @@ module Inspec
EXIT_USAGE_ERROR = 1
EXIT_PLUGIN_ERROR = 2
EXIT_FATAL_DEPRECATION = 3
EXIT_LICENSE_NOT_ACCEPTED = 172
EXIT_FAILED_TESTS = 100
EXIT_SKIPPED_TESTS = 101

View file

@ -0,0 +1,114 @@
require 'functional/helper'
require 'tmpdir'
require 'yaml'
describe 'The license acceptance mechanism' do
include FunctionalHelper
describe 'when the license has not been accepted' do
describe 'when the user passes the --chef-license accept flag' do
it 'should silently work normally' do
Dir.mktmpdir do |tmp_home|
run_result = run_inspec_process('shell -c platform.family --chef-license accept', env: { 'HOME' => tmp_home })
run_result.stdout.wont_include 'Chef License Acceptance' # --chef-license should not mention accepting the license
run_result.stderr.must_equal ''
run_result.exit_status.must_equal 0
end
end
it 'should write a YAML file' do
Dir.mktmpdir do |tmp_home|
license_persist_path = File.join(tmp_home, '.chef', 'accepted_licenses', 'inspec')
File.exist?(license_persist_path).must_equal false # Sanity check
run_result = run_inspec_process('shell -c platform.family --chef-license accept', env: { 'HOME' => tmp_home })
File.exist?(license_persist_path).must_equal true
license_persist_contents = YAML.load(File.read(license_persist_path))
license_persist_contents.keys.must_include 'accepting_product'
license_persist_contents['accepting_product'].must_equal 'inspec'
end
end
end
# Since the license-acceptance library detects TTYs, and changes behavior
# if not found, we can't test interactive acceptance anymore
describe 'when no mechanism is used to accept the license and we are non-interactive' do
it 'should exit ASAP with code 172' do
Dir.mktmpdir do |tmp_home|
run_result = run_inspec_process('shell -c platform.family', env: { 'HOME' => tmp_home })
# [2019-04-11T11:06:00-04:00] ERROR: InSpec cannot execute without accepting the license
run_result.stdout.must_include 'cannot execute'
run_result.stdout.must_include 'the license'
run_result.stdout.must_include 'ERROR' # From failure message
run_result.exit_status.must_equal 172
end
end
end
describe 'when a command is used that should not be gated on licensure' do
[
'-h', '--help', 'help', '', # Empty invocation is treated as `inspec help`
'-v', '--version', 'version',
].each do |ungated_invocation|
it "should not challenge for a license when running `inspec #{ungated_invocation}`" do
Dir.mktmpdir do |tmp_home|
run_result = run_inspec_process(ungated_invocation, env: { 'HOME' => tmp_home })
run_result.stdout.wont_include 'Chef License Acceptance'
run_result.stderr.must_equal ''
run_result.exit_status.must_equal 0
end
end
end
end
end
describe 'when the license has already been accepted' do
describe 'when the license was accepted by touching a blank file' do
it 'should silently work normally' do
Dir.mktmpdir do |tmp_home|
license_persist_dir = File.join(tmp_home, '.chef', 'accepted_licenses')
license_persist_path = File.join(tmp_home, '.chef', 'accepted_licenses', 'inspec')
File.exist?(license_persist_path).must_equal false # Sanity check
FileUtils.mkdir_p(license_persist_dir)
FileUtils.touch(license_persist_path)
File.exist?(license_persist_path).must_equal true # Sanity check
run_result = run_inspec_process('shell -c platform.family', env: { 'HOME' => tmp_home })
run_result.stdout.wont_include 'Chef License Acceptance'
run_result.stderr.must_equal ''
run_result.exit_status.must_equal 0
end
end
end
describe 'when the license persistance file is a YAML file' do
it 'should silently work normally' do
Dir.mktmpdir do |tmp_home|
license_persist_dir = File.join(tmp_home, '.chef', 'accepted_licenses')
license_persist_path = File.join(tmp_home, '.chef', 'accepted_licenses', 'inspec')
File.exist?(license_persist_path).must_equal false # Sanity check
FileUtils.mkdir_p(license_persist_dir)
File.write(license_persist_path, <<~EOY)
---
name: inspec
date_accepted: '1979-08-04T16:36:53-05:00'
accepting_product: inspec
accepting_product_version: 1.2.3
user: someone
file_format: 1
EOY
File.exist?(license_persist_path).must_equal true # Sanity check
run_result = run_inspec_process('shell -c platform.family', env: { 'HOME' => tmp_home })
run_result.stdout.wont_include 'Chef License Acceptance'
run_result.stderr.must_equal ''
run_result.exit_status.must_equal 0
end
end
end
end
end