diff --git a/lib/inspec/globals.rb b/lib/inspec/globals.rb index 93a802616..b41470a7c 100644 --- a/lib/inspec/globals.rb +++ b/lib/inspec/globals.rb @@ -1,9 +1,15 @@ +require_relative "utils/install_context" + module Inspec + + extend Inspec::InstallContextHelpers + def self.config_dir ENV["INSPEC_CONFIG_DIR"] ? ENV["INSPEC_CONFIG_DIR"] : File.join(Dir.home, ".inspec") end def self.src_root - File.expand_path(File.join(__FILE__, "..", "..", "..")) + @src_root ||= File.expand_path(File.join(__FILE__, "../../..")) end + end diff --git a/lib/inspec/utils/install_context.rb b/lib/inspec/utils/install_context.rb new file mode 100644 index 000000000..94c6fa553 --- /dev/null +++ b/lib/inspec/utils/install_context.rb @@ -0,0 +1,60 @@ +module Inspec + + # Heuristics to determine how InSpec was installed. + module InstallContextHelpers + + def guess_install_context + # These all work by simple path recognition + return "chef-workstation" if chef_workstation_install? + return "omnibus" if omnibus_install? + return "chefdk" if chefdk_install? + return "habitat" if habitat_install? + + # Order matters here - gem mode is easily mistaken for one of the above + return "docker" if docker_install? + return "rubygem" if rubygem_install? + + return "source" if source_install? + + "unknown" + end + + private + + def chef_workstation_install? + !!(src_root.start_with?("/opt/chef-workstation") || src_root.start_with?("C:/opscode/chef-workstation")) + end + + def chefdk_install? + !!(src_root.start_with?("/opt/chef-dk") || src_root.start_with?("C:/opscode/chef-dk")) + end + + def docker_install? + # Our docker image is based on alpine. This could be easily fooled. + !!(rubygem_install? && path_exist?("/etc/alpine-release")) && path_exist?("/.dockerenv") + end + + def habitat_install? + !!src_root.match(%r{hab/pkgs/chef/inspec/\d+\.\d+\.\d+/\d{14}}) + end + + def omnibus_install? + !!(src_root.start_with?("/opt/inspec") || src_root.start_with?("C:/opscode/inspec")) + end + + def rubygem_install? + !!src_root.match(%r{gems/inspec-\d+\.\d+\.\d+}) + end + + def source_install? + # These are a couple of examples of dirs removed during packaging + %w{habitat test}.all? do |devdir| + path_exist?("#{src_root}/#{devdir}") + end + end + + def path_exist?(path) + File.exist? path + end + end +end diff --git a/test/unit/utils/install_context_test.rb b/test/unit/utils/install_context_test.rb new file mode 100644 index 000000000..559f6446c --- /dev/null +++ b/test/unit/utils/install_context_test.rb @@ -0,0 +1,94 @@ +require "helper" +require "inspec/globals" +require "inspec/utils/install_context" + +def assert_install_contexts(test_obj, test_expected_to_be_true, also_rubygem) + should_be_false = %w{chef-workstation chefdk docker + habitat omnibus rubygem source} + should_be_false -= [test_expected_to_be_true] + should_be_false = should_be_false.map { |m| "#{m}_install?".tr("-", "_").to_sym } + should_be_false -= [:rubygem_install?] if also_rubygem + should_be_false.each { |m| _(test_obj).wont_be(m) } + + should_be_true = ["#{test_expected_to_be_true}_install?".tr("-", "_").to_sym] + should_be_true += [:rubygem_install?] if also_rubygem + should_be_true.each { |m| _(test_obj).must_be(m) } + + expect(test_obj.guess_install_context).must_equal test_expected_to_be_true +end + +class InstallContextTester + include Inspec::InstallContextHelpers + attr_accessor :src_root, :dummy_paths + def initialize(src_root: "", dummy_paths: []) + @src_root = src_root + @dummy_paths = dummy_paths + end + + def path_exist?(path) + if dummy_paths.empty? + File.exist? path + else + dummy_paths.include? path + end + end +end + +describe Inspec::InstallContextHelpers do + + parallelize_me! + + describe "when it looks like a Docker installation" do + it "should properly detect a Docker install" do + test_obj = InstallContextTester.new( + src_root: "/somewhere/gems/inspec-4.18.39", dummy_paths: [ "/etc/alpine-release", "/.dockerenv" ] + ) + assert_install_contexts(test_obj, "docker", true) + end + end + + describe "when it looks like a Habitat installation" do + it "should properly detect a habitat install" do + test_obj = InstallContextTester.new(src_root: "/hab/pkgs/chef/inspec/4.18.61/20200121194907/lib/gems/inspec-4.18.61") + assert_install_contexts(test_obj, "habitat", true) + end + end + + describe "when it looks like a gem installation" do + it "should properly detect a rubygem install" do + test_obj = InstallContextTester.new(src_root: "/Users/alice/.rbenv/versions/2.6.5/lib/ruby/gems/2.6.0/gems/inspec-4.18.61") + assert_install_contexts(test_obj, "rubygem", true) + end + end + + describe "when it looks like a source installation" do + it "should properly detect a source install" do + fake_root = "/Users/alice/src/inspec" + test_obj = InstallContextTester.new( + src_root: fake_root, + dummy_paths: [ "#{fake_root}/habitat", "#{fake_root}/test" ] + ) + assert_install_contexts(test_obj, "source", false) + end + end + + { + "Windows" => "C:/opscode", + "Unix-like" => "/opt", + }.each do |os_name, inst_dir| + describe "when on #{os_name} machines" do + { + "chef-workstation" => "chef-workstation", + "chefdk" => "chef-dk", + "omnibus" => "inspec", + }.each do |inst_mode, inst_subdir| + describe "when it looks like a #{inst_mode} installation" do + it "should properly detect a #{os_name} #{inst_mode} install" do + test_obj = InstallContextTester.new(src_root: "#{inst_dir}/#{inst_subdir}/embedded/lib/ruby/gems/2.6.0/gems/inspec-4.18.39") + assert_install_contexts(test_obj, inst_mode, true) + end + end + end + end + end +end