diff --git a/lib/chef-cli/command/env.rb b/lib/chef-cli/command/env.rb index b157d557..8b6477c0 100644 --- a/lib/chef-cli/command/env.rb +++ b/lib/chef-cli/command/env.rb @@ -40,19 +40,35 @@ def initialize(*args) def run(params) info = {} - info[ChefCLI::Dist::PRODUCT] = workstation_info + product_name = get_product_info + info[product_name] = workstation_info info["Ruby"] = ruby_info info["Path"] = paths ui.msg YAML.dump(info) end + def get_product_info + if omnibus_install? + ChefCLI::Dist::PRODUCT + elsif habitat_chef_dke? + ChefCLI::Dist::CHEF_DK_CLI_PACKAGE + elsif habitat_standalone? + ChefCLI::Dist::CHEF_CLI_PACKAGE + else + ChefCLI::Dist::PRODUCT + end + end + def workstation_info - info = {} + info = { "Version" => ChefCLI::VERSION } if omnibus_install? - info["Version"] = ChefCLI::VERSION info["Home"] = package_home info["Install Directory"] = omnibus_root info["Policyfile Config"] = policyfile_config + elsif habitat_chef_dke? || habitat_standalone? + info["Home"] = package_home + info["Install Directory"] = get_pkg_install_path + info["Policyfile Config"] = policyfile_config else info["Version"] = "Not running from within Workstation" end @@ -73,19 +89,27 @@ def ruby_info def gem_environment h = {} - h["GEM ROOT"] = omnibus_env["GEM_ROOT"] - h["GEM HOME"] = omnibus_env["GEM_HOME"] - h["GEM PATHS"] = omnibus_env["GEM_PATH"].split(File::PATH_SEPARATOR) - rescue OmnibusInstallNotFound - h["GEM_ROOT"] = ENV["GEM_ROOT"] if ENV.key?("GEM_ROOT") - h["GEM_HOME"] = ENV["GEM_HOME"] if ENV.key?("GEM_HOME") - h["GEM PATHS"] = ENV["GEM_PATH"].split(File::PATH_SEPARATOR) if ENV.key?("GEM_PATH") && !ENV.key?("GEM_PATH").nil? - ensure + if habitat_install? + # Habitat-specific environment variables + h["GEM ROOT"] = habitat_env(show_warning: true)["GEM_ROOT"] + h["GEM HOME"] = habitat_env(show_warning: true)["GEM_HOME"] + h["GEM PATHS"] = habitat_env(show_warning: true)["GEM_PATH"].split(File::PATH_SEPARATOR) + elsif omnibus_install? + # Omnibus-specific environment variables + h["GEM ROOT"] = omnibus_env["GEM_ROOT"] + h["GEM HOME"] = omnibus_env["GEM_HOME"] + h["GEM PATHS"] = omnibus_env["GEM_PATH"].split(File::PATH_SEPARATOR) + else + # Fallback to system environment variables if neither Omnibus nor Habitat + h["GEM_ROOT"] = ENV["GEM_ROOT"] if ENV.key?("GEM_ROOT") + h["GEM_HOME"] = ENV["GEM_HOME"] if ENV.key?("GEM_HOME") + h["GEM PATHS"] = ENV["GEM_PATH"].split(File::PATH_SEPARATOR) if ENV.key?("GEM_PATH") && !ENV["GEM_PATH"].nil? + end h end def paths - env = habitat_install? ? habitat_env : omnibus_env + env = habitat_install? ? habitat_env(show_warning: true) : omnibus_env env["PATH"].split(File::PATH_SEPARATOR) rescue OmnibusInstallNotFound ENV["PATH"].split(File::PATH_SEPARATOR) diff --git a/lib/chef-cli/dist.rb b/lib/chef-cli/dist.rb index a0a31856..e253463f 100644 --- a/lib/chef-cli/dist.rb +++ b/lib/chef-cli/dist.rb @@ -6,6 +6,9 @@ class Dist PRODUCT = "Chef Workstation".freeze PRODUCT_PKG_HOME = "chef-workstation".freeze + CHEF_DK_CLI_PACKAGE = "Chef Development Kit Enterprise".freeze + CHEF_CLI_PACKAGE = "Chef-Cli".freeze + # the name of the chef-cli gem CLI_PRODUCT = "Chef CLI".freeze CLI_GEM = "chef-cli".freeze diff --git a/lib/chef-cli/helpers.rb b/lib/chef-cli/helpers.rb index f3edc533..2221cc8a 100644 --- a/lib/chef-cli/helpers.rb +++ b/lib/chef-cli/helpers.rb @@ -98,6 +98,32 @@ def package_home end end + # Function to return the Chef CLI path based on standalone or Chef-DKE-enabled package + def get_pkg_install_path + # Check Chef-DKE package path + chef_dk_path = get_pkg_prefix(ChefCLI::Dist::CHEF_DKE_PKG_NAME) + return chef_dk_path if chef_dk_path + + # Check Standalone Chef-CLI package path + chef_cli_path = fetch_chef_cli_version_pkg || get_pkg_prefix(ChefCLI::Dist::HAB_PKG_NAME) + chef_cli_path + + rescue => e + ChefCLI::UI.new.err("Error fetching Chef-CLI path: #{e.message}") + nil + end + + # Check Standalone Chef-cli environment variable for version + def fetch_chef_cli_version_pkg + chef_cli_version = ENV["CHEF_CLI_VERSION"] + return unless chef_cli_version + + pkg_path = get_pkg_prefix("#{ChefCLI::Dist::HAB_PKG_NAME}/#{chef_cli_version}") + return pkg_path if pkg_path && Dir.exist?(pkg_path) + + nil + end + # Returns the directory that contains our main symlinks. # On Mac we place all of our symlinks under /usr/local/bin on other # platforms they are under /usr/bin @@ -128,14 +154,27 @@ def git_windows_bin_dir # # environment vars for habitat # - def habitat_env + def habitat_env(show_warning: false) @habitat_env ||= begin - # Define the necessary paths for the Habitat environment - # If it is a chef-dke installation, we will use the chef-dke bin path. - # Otherwise, we will use the chef-cli bin path. - bin_pkg_prefix = get_pkg_prefix(habitat_chef_dke? ? ChefCLI::Dist::CHEF_DKE_PKG_NAME : ChefCLI::Dist::HAB_PKG_NAME) - vendor_dir = File.join(get_pkg_prefix(ChefCLI::Dist::HAB_PKG_NAME), "vendor") + if habitat_chef_dke? + bin_pkg_prefix = get_pkg_prefix(ChefCLI::Dist::CHEF_DKE_PKG_NAME) + end + versioned_pkg_prefix = fetch_chef_cli_version_pkg if ENV["CHEF_CLI_VERSION"] + + if show_warning && ENV["CHEF_CLI_VERSION"] && !versioned_pkg_prefix + ChefCLI::UI.new.msg("Warning: Habitat package '#{ChefCLI::Dist::HAB_PKG_NAME}' with version '#{ENV["CHEF_CLI_VERSION"]}' not found.") + end + # Use the first available package for bin_pkg_prefix + bin_pkg_prefix ||= versioned_pkg_prefix || get_pkg_prefix(ChefCLI::Dist::HAB_PKG_NAME) + raise "Error: Could not determine the Habitat package prefix. Ensure #{ChefCLI::Dist::HAB_PKG_NAME} is installed and CHEF_CLI_VERSION is set correctly." unless bin_pkg_prefix + + # Determine vendor_dir by prioritizing the versioned package first + vendor_pkg_prefix = versioned_pkg_prefix || get_pkg_prefix(ChefCLI::Dist::HAB_PKG_NAME) + raise "Error: Could not determine the vendor package prefix. Ensure #{ChefCLI::Dist::HAB_PKG_NAME} is installed and CHEF_CLI_VERSION is set correctly." unless vendor_pkg_prefix + + vendor_dir = File.join(vendor_pkg_prefix, "vendor") + # Construct PATH path = [ File.join(bin_pkg_prefix, "bin"), ENV["PATH"].split(File::PATH_SEPARATOR), # Preserve existing PATH @@ -144,8 +183,8 @@ def habitat_env { "PATH" => path.join(File::PATH_SEPARATOR), "GEM_ROOT" => Gem.default_dir, # Default directory for gems - "GEM_HOME" => vendor_dir, # GEM_HOME pointing to the vendor directory - "GEM_PATH" => vendor_dir, # GEM_PATH also pointing to the vendor directory + "GEM_HOME" => vendor_dir, # Set only if vendor_dir exists + "GEM_PATH" => vendor_dir, # Set only if vendor_dir exists } end end @@ -161,22 +200,17 @@ def omnibus_env path << git_bin_dir if Dir.exist?(git_bin_dir) path << git_windows_bin_dir if Dir.exist?(git_windows_bin_dir) { - "PATH" => path.flatten.uniq.join(File::PATH_SEPARATOR), - "GEM_ROOT" => Gem.default_dir, - "GEM_HOME" => Gem.user_dir, - "GEM_PATH" => Gem.path.join(File::PATH_SEPARATOR), + "PATH" => path.flatten.uniq.join(File::PATH_SEPARATOR), + "GEM_ROOT" => Gem.default_dir, + "GEM_HOME" => Gem.user_dir, + "GEM_PATH" => Gem.path.join(File::PATH_SEPARATOR), } end end def get_pkg_prefix(pkg_name) - path = `hab pkg path #{pkg_name}`.strip - - if $?.success? && !path.empty? - path - else - raise "Failed to get pkg_prefix for #{pkg_name}: #{path}" - end + path = `hab pkg path #{pkg_name} 2>/dev/null`.strip + path if !path.empty? && Dir.exist?(path) # Return path only if it exists end def omnibus_expand_path(*paths) diff --git a/spec/unit/command/env_spec.rb b/spec/unit/command/env_spec.rb index 4572ed97..41e0d201 100644 --- a/spec/unit/command/env_spec.rb +++ b/spec/unit/command/env_spec.rb @@ -39,6 +39,7 @@ allow(command_instance).to receive(:omnibus_install?).and_return true allow(command_instance).to receive(:omnibus_embedded_bin_dir).and_return(omnibus_embedded_bin_dir) allow(command_instance).to receive(:omnibus_bin_dir).and_return(omnibus_bin_dir) + allow(command_instance).to receive(:get_product_info).and_return(ChefCLI::Dist::PRODUCT) command_instance.ui = ui end @@ -81,6 +82,134 @@ end end end + + describe "when running in a Chef-cli Habitat Standalone package" do + let(:standalone_pkg_base) { "/hab/pkgs/chef/chef-cli" } + let(:standalone_pkg_version) { "1.0.0" } + let(:standalone_pkg_build) { "20240210120000" } + let(:standalone_pkg_path) { "#{standalone_pkg_base}/#{standalone_pkg_version}/#{standalone_pkg_build}" } + + let(:ruby_version) { "3.1.0" } + let(:ruby_base) { "/hab/pkgs/core/ruby/#{ruby_version}/20240101000000/lib/ruby/gems" } + let(:cli_gem_home) { "/hab/pkgs/chef/chef-cli/#{standalone_pkg_version}/20240210121000/vendor/bundle/ruby/#{ruby_version}" } + + before do + allow(command_instance).to receive(:habitat_install?).and_return(true) + allow(command_instance).to receive(:habitat_standalone?).and_return(true) + allow(command_instance).to receive(:habitat_chef_dke?).and_return(false) + allow(command_instance).to receive(:omnibus_install?).and_return(false) + allow(command_instance).to receive(:get_product_info).and_return(ChefCLI::Dist::CHEF_CLI_PACKAGE) + + allow(command_instance).to receive(:get_pkg_install_path).and_return(standalone_pkg_path) + + allow(command_instance).to receive(:habitat_env).and_return({ + "GEM_ROOT" => ruby_base, + "GEM_HOME" => cli_gem_home, + "GEM_PATH" => cli_gem_home, + "PATH" => "#{standalone_pkg_path}/bin:/usr/local/bin:/usr/bin" + }) + + command_instance.ui = ui + end + + describe "and the env command is run" do + let(:yaml) { YAML.load(ui.output) } + + before :each do + run_command + end + + it "should include correct chef-cli hab pkg name" do + expect(yaml).to have_key(ChefCLI::Dist::CHEF_CLI_PACKAGE) + end + + it "should include correct chef-cli hab pkg version info" do + expect(yaml[ChefCLI::Dist::CHEF_CLI_PACKAGE]["Version"]).to eql ChefCLI::VERSION + end + + it "should include correct Habitat installation path" do + expect(yaml[ChefCLI::Dist::CHEF_CLI_PACKAGE]["Install Directory"]).to eql standalone_pkg_path + end + + it "should include correct GEM_ROOT path" do + expect(yaml["Ruby"]["RubyGems"]["Gem Environment"]["GEM ROOT"]).to eql ruby_base + end + + it "should include correct GEM_HOME path" do + expect(yaml["Ruby"]["RubyGems"]["Gem Environment"]["GEM HOME"]).to eql cli_gem_home + end + + it "should include correct GEM_PATH paths" do + expect(yaml["Ruby"]["RubyGems"]["Gem Environment"]["GEM PATHS"]).to eql [cli_gem_home] + end + end + end + + describe "when running chef-cli coming with Chef-DKE Habitat package" do + let(:hab_pkg_base) { "/hab/pkgs/chef/chef-development-kit-enterprise" } + let(:hab_pkg_version) { "1.0.0" } + let(:hab_pkg_build) { "20240210120000" } + let(:hab_pkg_path) { "#{hab_pkg_base}/#{hab_pkg_version}/#{hab_pkg_build}" } + + let(:ruby_version) { "3.1.0" } + let(:ruby_base) { "/hab/pkgs/core/ruby/#{ruby_version}/20240101000000/lib/ruby/gems" } + let(:cli_gem_home) { "/hab/pkgs/chef/chef-cli/#{hab_pkg_version}/20240210121000/vendor/bundle/ruby/#{ruby_version}" } + + before do + # Mock all Habitat-related methods + allow(command_instance).to receive(:habitat_install?).and_return true + allow(command_instance).to receive(:habitat_chef_dke?).and_return true + allow(command_instance).to receive(:habitat_standalone?).and_return false + allow(command_instance).to receive(:omnibus_install?).and_return false + allow(command_instance).to receive(:get_product_info).and_return(ChefCLI::Dist::CHEF_DK_CLI_PACKAGE) + + # Mock Habitat package paths + allow(command_instance).to receive(:get_pkg_install_path).and_return(hab_pkg_path) + + # Mock habitat_env to reflect correct GEM paths + allow(command_instance).to receive(:habitat_env).and_return({ + "GEM_ROOT" => ruby_base, + "GEM_HOME" => cli_gem_home, + "GEM_PATH" => cli_gem_home, + "PATH" => "#{hab_pkg_path}/bin:/usr/local/bin:/usr/bin" + }) + + command_instance.ui = ui + end + + describe "and the env command is run" do + let(:yaml) { YAML.load(ui.output) } + + before :each do + run_command + end + + it "should include correct product name for Chef-DKE Habitat package" do + expect(yaml).to have_key(ChefCLI::Dist::CHEF_DK_CLI_PACKAGE) + end + + it "should include correct version" do + expect(yaml[ChefCLI::Dist::CHEF_DK_CLI_PACKAGE]["Version"]).to eql ChefCLI::VERSION + end + + it "should include correct Habitat installation path" do + expect(yaml[ChefCLI::Dist::CHEF_DK_CLI_PACKAGE]["Install Directory"]).to eql hab_pkg_path + end + + it "should include correct GEM_ROOT path" do + expect(yaml["Ruby"]["RubyGems"]["Gem Environment"]["GEM ROOT"]).to eql ruby_base + end + + it "should include correct GEM_HOME path" do + expect(yaml["Ruby"]["RubyGems"]["Gem Environment"]["GEM HOME"]).to eql cli_gem_home + end + + it "should include correct GEM_PATH paths" do + expect(yaml["Ruby"]["RubyGems"]["Gem Environment"]["GEM PATHS"]).to eql [cli_gem_home] + end + end + end + def run_command command_instance.run_with_default_options(false, command_options) end diff --git a/spec/unit/helpers_spec.rb b/spec/unit/helpers_spec.rb index 0a71e312..bd8c9a4a 100644 --- a/spec/unit/helpers_spec.rb +++ b/spec/unit/helpers_spec.rb @@ -113,10 +113,10 @@ let(:chef_dke_path) { "/hab/pkgs/chef/chef-development-kit-enterprise/1.0.0/123" } let(:cli_hab_path) { "/hab/pkgs/chef/chef-cli/1.0.0/123" } let(:expected_gem_root) { Gem.default_dir } - let(:expected_path) { %W{#{chef_dke_path}/bin /usr/bin:/bin} } + let(:expected_path) { [File.join(chef_dke_path, "bin"), "/usr/bin:/bin"].flatten } let(:expected_env) do { - "PATH" => expected_path.join(File::PATH_SEPARATOR) , + "PATH" => expected_path.join(File::PATH_SEPARATOR), "GEM_ROOT" => expected_gem_root, "GEM_HOME" => "#{cli_hab_path}/vendor", "GEM_PATH" => "#{cli_hab_path}/vendor", @@ -127,14 +127,18 @@ allow(ChefCLI::Helpers).to receive(:habitat_chef_dke?).and_return true allow(ChefCLI::Helpers).to receive(:habitat_standalone?).and_return false allow(ENV).to receive(:[]).with("PATH").and_return("/usr/bin:/bin") + allow(ENV).to receive(:[]).with("CHEF_CLI_VERSION").and_return(nil) + allow(Dir).to receive(:exist?).with("#{cli_hab_path}/vendor").and_return(true) # <-- Add this line end it "should return the habitat env" do + allow(ChefCLI::Helpers).to receive(:fetch_chef_cli_version_pkg).and_return(nil) # Ensure no version override expect(ChefCLI::Helpers).to receive(:get_pkg_prefix).with("chef/chef-development-kit-enterprise").and_return(chef_dke_path) expect(ChefCLI::Helpers).to receive(:get_pkg_prefix).with("chef/chef-cli").and_return(cli_hab_path) expect(ChefCLI::Helpers.habitat_env).to eq(expected_env) end end + end end