diff options
author | Tim Smith <tsmith@chef.io> | 2018-07-13 10:37:34 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-07-13 10:37:34 -0700 |
commit | d99bc319e244e4d4fac57bc1853ea82f8b97c0e9 (patch) | |
tree | 77eac01f6baac6486308a30c8c3982f03309fb39 | |
parent | 8baff8ae8f6dc0e88c1a467566b1ac595ceef267 (diff) | |
parent | 6fb425078b5986998c37450445f5b3ded61e02ac (diff) | |
download | chef-d99bc319e244e4d4fac57bc1853ea82f8b97c0e9.tar.gz |
Merge pull request #7455 from coderanger/config-commands
Add knife config get/use-profile commands
-rw-r--r-- | RELEASE_NOTES.md | 26 | ||||
-rw-r--r-- | chef-config/lib/chef-config/mixin/credentials.rb | 91 | ||||
-rw-r--r-- | lib/chef/knife.rb | 8 | ||||
-rw-r--r-- | lib/chef/knife/config_get_profile.rb | 37 | ||||
-rw-r--r-- | lib/chef/knife/config_list_profiles.rb | 121 | ||||
-rw-r--r-- | lib/chef/knife/config_use_profile.rb | 50 | ||||
-rw-r--r-- | lib/chef/knife/core/subcommand_loader.rb | 3 | ||||
-rw-r--r-- | spec/integration/knife/config_get_profile_spec.rb | 112 | ||||
-rw-r--r-- | spec/integration/knife/config_list_profiles_spec.rb | 188 | ||||
-rw-r--r-- | spec/integration/knife/config_use_profile_spec.rb | 100 |
10 files changed, 708 insertions, 28 deletions
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index dbc44a93f3..68a31e9882 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -1,5 +1,31 @@ This file holds "in progress" release notes for the current release under development and is intended for consumption by the Chef Documentation team. Please see <https://docs.chef.io/release_notes.html> for the official Chef release notes. +## Knife configuration profile management commands + +Several new commands have been added under `knife config` to help manage multiple +profiles in your `credentials` file. + +`knife config get-profile` will display the active profile. + +`knife config use-profile PROFILE` will set the workstation-level default +profile. That default can still be overridden by the `--profile` command line +option or the `$CHEF_PROFILE` environment variable. + +`knife config list-profiles` will display all your available profiles along with +summary information on each. + +```bash +$ knife config get-profile +staging +$ knife config use-profile prod +Set default profile to prod +$ knife config list-profiles + Profile Client Key Server +----------------------------------------------------------------------------- + staging myuser ~/.chef/user.pem https://example.com/organizations/staging +*prod myuser ~/.chef/user.pem https://example.com/organizations/prod +``` + # Chef Client Release Notes 14.3: ## New Preview Resources Concept diff --git a/chef-config/lib/chef-config/mixin/credentials.rb b/chef-config/lib/chef-config/mixin/credentials.rb index 5a73a49add..0a7ca356c8 100644 --- a/chef-config/lib/chef-config/mixin/credentials.rb +++ b/chef-config/lib/chef-config/mixin/credentials.rb @@ -20,37 +20,78 @@ require "chef-config/path_helper" module ChefConfig module Mixin + # Helper methods for working with credentials files. + # + # @since 13.7 + # @api internal module Credentials - - def load_credentials(profile = nil) - credentials_file = PathHelper.home(".chef", "credentials").freeze + # Compute the active credentials profile name. + # + # The lookup order is argument (from --profile), environment variable + # ($CHEF_PROFILE), context file (~/.chef/context), and then "default" as + # a fallback. + # + # @since 14.4 + # @param profile [String, nil] Optional override for the active profile, + # normally set via a command-line option. + # @return [String] + def credentials_profile(profile = nil) context_file = PathHelper.home(".chef", "context").freeze + if !profile.nil? + profile + elsif ENV.include?("CHEF_PROFILE") + ENV["CHEF_PROFILE"] + elsif File.file?(context_file) + File.read(context_file).strip + else + "default" + end + end - return unless File.file?(credentials_file) - - context = File.read(context_file).strip if File.file?(context_file) - - environment = ENV.fetch("CHEF_PROFILE", nil) + # Compute the path to the credentials file. + # + # @since 14.4 + # @return [String] + def credentials_file_path + PathHelper.home(".chef", "credentials").freeze + end - profile = if !profile.nil? - profile - elsif !environment.nil? - environment - elsif !context.nil? - context - else - "default" - end + # Load and parse the credentials file. + # + # Returns `nil` if the credentials file is unavailable. + # + # @since 14.4 + # @return [String, nil] + def parse_credentials_file + credentials_file = credentials_file_path + return nil unless File.file?(credentials_file) + begin + Tomlrb.load_file(credentials_file) + rescue => e + # TOML's error messages are mostly rubbish, so we'll just give a generic one + message = "Unable to parse Credentials file: #{credentials_file}\n" + message << e.message + raise ChefConfig::ConfigurationError, message + end + end - config = Tomlrb.load_file(credentials_file) + # Load and process the active credentials. + # + # @see WorkstationConfigLoader#apply_credentials + # @param profile [String, nil] Optional override for the active profile, + # normally set via a command-line option. + # @return [void] + def load_credentials(profile = nil) + profile = credentials_profile(profile) + config = parse_credentials_file + return if config.nil? # No credentials, nothing to do here. + if config[profile].nil? + # Unknown profile name. For "default" just silently ignore, otherwise + # raise an error. + return if profile == "default" + raise ChefConfig::ConfigurationError, "Profile #{profile} doesn't exist. Please add it to #{credentials_file}." + end apply_credentials(config[profile], profile) - rescue ChefConfig::ConfigurationError - raise - rescue => e - # TOML's error messages are mostly rubbish, so we'll just give a generic one - message = "Unable to parse Credentials file: #{credentials_file}\n" - message << e.message - raise ChefConfig::ConfigurationError, message end end end diff --git a/lib/chef/knife.rb b/lib/chef/knife.rb index e28ca81f71..6e525bdf3d 100644 --- a/lib/chef/knife.rb +++ b/lib/chef/knife.rb @@ -304,8 +304,12 @@ class Chef # knife node run_list add requires that we have extra logic to handle # the case that command name words could be joined by an underscore :/ - command_name_words = command_name_words.join("_") - @name_args.reject! { |name_arg| command_name_words == name_arg } + command_name_joined = command_name_words.join("_") + @name_args.reject! { |name_arg| command_name_joined == name_arg } + + # Similar handling for hyphens. + command_name_joined = command_name_words.join("-") + @name_args.reject! { |name_arg| command_name_joined == name_arg } if config[:help] msg opt_parser diff --git a/lib/chef/knife/config_get_profile.rb b/lib/chef/knife/config_get_profile.rb new file mode 100644 index 0000000000..309b7f81e8 --- /dev/null +++ b/lib/chef/knife/config_get_profile.rb @@ -0,0 +1,37 @@ +# +# Copyright:: Copyright (c) 2018, Noah Kantrowitz +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require "chef/knife" + +class Chef + class Knife + class ConfigGetProfile < Knife + banner "knife config get-profile" + + # Disable normal config loading since this shouldn't fail if the profile + # doesn't exist of the config is otherwise corrupted. + def configure_chef + apply_computed_config + end + + def run + ui.msg(self.class.config_loader.credentials_profile(config[:profile])) + end + + end + end +end diff --git a/lib/chef/knife/config_list_profiles.rb b/lib/chef/knife/config_list_profiles.rb new file mode 100644 index 0000000000..16b0c5df27 --- /dev/null +++ b/lib/chef/knife/config_list_profiles.rb @@ -0,0 +1,121 @@ +# +# Copyright:: Copyright (c) 2018, Noah Kantrowitz +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require "chef/knife" +require "chef/workstation_config_loader" + +class Chef + class Knife + class ConfigListProfiles < Knife + banner "knife config list-profiles" + + option :ignore_knife_rb, + short: "-i", + long: "--ignore-knife-rb", + description: "Ignore the current knife.rb configuration", + default: false + + def run + credentials_data = self.class.config_loader.parse_credentials_file + if credentials_data.nil? || credentials_data.empty? + # Should this just show the ambient knife.rb config as "default" instead? + ui.fatal("No profiles found, #{self.class.config_loader.credentials_file_path} does not exist or is empty") + exit 1 + end + + current_profile = self.class.config_loader.credentials_profile(config[:profile]) + profiles = credentials_data.keys.map do |profile| + if config[:ignore_knife_rb] + # Don't do any fancy loading nonsense, just the raw data. + profile_data = credentials_data[profile] + { + profile: profile, + active: profile == current_profile, + client_name: profile_data["client_name"] || profile_data["node_name"], + client_key: profile_data["client_key"], + server_url: profile_data["chef_server_url"], + } + else + # Fancy loading nonsense so we get what the actual config would be. + # Note that this modifies the global config, after this, all bets are + # off as to whats in the config. + Chef::Config.reset + wcl = Chef::WorkstationConfigLoader.new(nil, Chef::Log, profile: profile) + wcl.load + { + profile: profile, + active: profile == current_profile, + client_name: Chef::Config[:node_name], + client_key: Chef::Config[:client_key], + server_url: Chef::Config[:chef_server_url], + } + end + end + + # Try to reset the config. + unless config[:ignore_knife_rb] + Chef::Config.reset + Chef::WorkstationConfigLoader.new(config[:config_file], Chef::Log, profile: config[:profile]).load + apply_computed_config + end + + if ui.interchange? + # Machine-readable output. + ui.output(profiles) + else + # Table output. + ui.output(render_table(profiles)) + end + end + + private + + def render_table(profiles, padding: 2) + # Replace the home dir in the client key path with ~. + profiles.each do |profile| + profile[:client_key] = profile[:client_key].to_s.gsub(/^#{Regexp.escape(Dir.home)}/, "~") if profile[:client_key] + end + # Render the data to a 2D array that will be used for the table. + table_data = [["", "Profile", "Client", "Key", "Server"]] + profiles.map do |profile| + [profile[:active] ? "*" : ""] + profile.values_at(:profile, :client_name, :client_key, :server_url).map(&:to_s) + end + # Compute column widths. + column_widths = Array.new(table_data.first.length) do |i| + table_data.map { |row| row[i].length + padding }.max + end + # Special case, the first col gets no padding (because indicator) and last + # get no padding because last. + column_widths[0] -= padding + column_widths[-1] -= padding + # Build the format string for each row. + format_string = column_widths.map { |w| "%-#{w}.#{w}s" }.join("") + format_string << "\n" + # Print the header row and a separator. + table = ui.color(format_string % table_data.first, :green) + table << "-" * column_widths.sum + table << "\n" + # Print the rest of the table. + table_data.drop(1).each do |row| + table << format_string % row + end + # Trim the last newline because ui.output adds one. + table.chomp! + end + + end + end +end diff --git a/lib/chef/knife/config_use_profile.rb b/lib/chef/knife/config_use_profile.rb new file mode 100644 index 0000000000..515c4a5336 --- /dev/null +++ b/lib/chef/knife/config_use_profile.rb @@ -0,0 +1,50 @@ +# +# Copyright:: Copyright (c) 2018, Noah Kantrowitz +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require "fileutils" + +require "chef/knife" + +class Chef + class Knife + class ConfigUseProfile < Knife + banner "knife config use-profile PROFILE" + + # Disable normal config loading since this shouldn't fail if the profile + # doesn't exist of the config is otherwise corrupted. + def configure_chef + apply_computed_config + end + + def run + context_file = ChefConfig::PathHelper.home(".chef", "context").freeze + profile = @name_args[0]&.strip + if profile && !profile.empty? + # Ensure the .chef/ folder exists. + FileUtils.mkdir_p(File.dirname(context_file)) + IO.write(context_file, "#{profile}\n") + ui.msg("Set default profile to #{profile}") + else + show_usage + ui.fatal("You must specify a profile") + exit 1 + end + end + + end + end +end diff --git a/lib/chef/knife/core/subcommand_loader.rb b/lib/chef/knife/core/subcommand_loader.rb index 026967d6ec..fb3723de50 100644 --- a/lib/chef/knife/core/subcommand_loader.rb +++ b/lib/chef/knife/core/subcommand_loader.rb @@ -139,9 +139,10 @@ class Chef # hash composed of the given words joined by the separator. # def find_longest_key(hash, words, sep = "_") + words = words.dup match = nil until match || words.empty? - candidate = words.join(sep) + candidate = words.join(sep).tr("-", "_") if hash.key?(candidate) match = candidate else diff --git a/spec/integration/knife/config_get_profile_spec.rb b/spec/integration/knife/config_get_profile_spec.rb new file mode 100644 index 0000000000..e97b24b869 --- /dev/null +++ b/spec/integration/knife/config_get_profile_spec.rb @@ -0,0 +1,112 @@ +# +# Copyright 2018, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require "support/shared/integration/integration_helper" +require "support/shared/context/config" + +describe "knife config get-profile", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + include_context "with a chef repo" + + let(:cmd_args) { [] } + + subject do + cmd = knife("config", "get-profile", *cmd_args, instance_filter: lambda { |instance| + # Fake the failsafe check because this command doesn't actually process knife.rb. + $__KNIFE_INTEGRATION_FAILSAFE_CHECK << " ole" + }) + cmd.stdout + end + + around do |ex| + # Store and reset the value of some env vars. + old_chef_home = ENV["CHEF_HOME"] + old_knife_home = ENV["KNIFE_HOME"] + old_home = ENV["HOME"] + old_wd = Dir.pwd + ChefConfig::PathHelper.per_tool_home_environment = "KNIFE_HOME" + # Clear these out because they are cached permanently. + ChefConfig::PathHelper.class_exec { remove_class_variable(:@@home_dir) } + Chef::Knife::ConfigGetProfile.reset_config_loader! + begin + ex.run + ensure + ENV["CHEF_HOME"] = old_chef_home + ENV["KNIFE_HOME"] = old_knife_home + ENV["HOME"] = old_home + Dir.chdir(old_wd) + ENV[ChefConfig.windows? ? "CD" : "PWD"] = Dir.pwd + ChefConfig::PathHelper.per_tool_home_environment = nil + end + end + + before do + # Always run from the temp folder. This can't be in the `around` block above + # because it has to run after the before set in the "with a chef repo" shared context. + directory("repo") + Dir.chdir(path_to("repo")) + ENV[ChefConfig.windows? ? "CD" : "PWD"] = Dir.pwd + ENV["HOME"] = path_to(".") + end + + context "with no configuration" do + it { is_expected.to eq "default\n" } + end + + context "with --profile" do + let(:cmd_args) { %w{--profile production} } + it { is_expected.to eq "production\n" } + end + + context "with an environment variable" do + around do |ex| + old_chef_profile = ENV["CHEF_PROFILE"] + begin + ENV["CHEF_PROFILE"] = "staging" + ex.run + ensure + ENV["CHEF_PROFILE"] = old_chef_profile + end + end + + it { is_expected.to eq "staging\n" } + end + + context "with a context file" do + before { file(".chef/context", "development\n") } + it { is_expected.to eq "development\n" } + end + + context "with a context file under $CHEF_HOME" do + before do + file("chefhome/.chef/context", "other\n") + ENV["CHEF_HOME"] = path_to("chefhome") + end + + it { is_expected.to eq "other\n" } + end + + context "with a context file under $KNIFE_HOME" do + before do + file("knifehome/.chef/context", "other\n") + ENV["KNIFE_HOME"] = path_to("knifehome") + end + + it { is_expected.to eq "other\n" } + end +end diff --git a/spec/integration/knife/config_list_profiles_spec.rb b/spec/integration/knife/config_list_profiles_spec.rb new file mode 100644 index 0000000000..32846f9999 --- /dev/null +++ b/spec/integration/knife/config_list_profiles_spec.rb @@ -0,0 +1,188 @@ +# +# Copyright 2018, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require "support/shared/integration/integration_helper" +require "support/shared/context/config" + +describe "knife config list-profiles", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + include_context "with a chef repo" + + let(:cmd_args) { [] } + let(:knife_list_profiles) do + knife("config", "list-profiles", *cmd_args, instance_filter: proc { + # Clear the stub set up in KnifeSupport. + allow(File).to receive(:file?).and_call_original + }) + end + subject { knife_list_profiles.stdout } + + around do |ex| + # Store and reset the value of some env vars. + old_home = ENV["HOME"] + old_wd = Dir.pwd + # Clear these out because they are cached permanently. + ChefConfig::PathHelper.class_exec { remove_class_variable(:@@home_dir) } + Chef::Knife::ConfigListProfiles.reset_config_loader! + begin + ex.run + ensure + ENV["HOME"] = old_home + Dir.chdir(old_wd) + ENV[ChefConfig.windows? ? "CD" : "PWD"] = Dir.pwd + end + end + + before do + # Always run from the temp folder. This can't be in the `around` block above + # because it has to run after the before set in the "with a chef repo" shared context. + directory("repo") + Dir.chdir(path_to("repo")) + ENV[ChefConfig.windows? ? "CD" : "PWD"] = Dir.pwd + ENV["HOME"] = path_to(".") + end + + # NOTE: The funky formatting with # at the end of the line of some of the + # output examples are because of how the format strings are built, there is + # substantial trailing whitespace in most cases which many editors "helpfully" remove. + + context "with no credentials file" do + subject { knife_list_profiles.stderr } + it { is_expected.to eq "FATAL: No profiles found, #{path_to(".chef/credentials")} does not exist or is empty\n" } + end + + context "with an empty credentials file" do + before { file(".chef/credentials", "") } + subject { knife_list_profiles.stderr } + it { is_expected.to eq "FATAL: No profiles found, #{path_to(".chef/credentials")} does not exist or is empty\n" } + end + + context "with a simple default profile" do + before { file(".chef/credentials", <<~EOH) } + [default] + client_name = "testuser" + client_key = "testkey.pem" + chef_server_url = "https://example.com/organizations/testorg" + EOH + it { is_expected.to eq <<~EOH.delete("#") } + Profile Client Key Server # + ----------------------------------------------------------------------------------# + *default testuser ~/.chef/testkey.pem https://example.com/organizations/testorg# + EOH + end + + context "with multiple profiles" do + before { file(".chef/credentials", <<~EOH) } + [default] + client_name = "testuser" + client_key = "testkey.pem" + chef_server_url = "https://example.com/organizations/testorg" + + [prod] + client_name = "testuser" + client_key = "testkey.pem" + chef_server_url = "https://example.com/organizations/prod" + + [qa] + client_name = "qauser" + client_key = "~/src/qauser.pem" + chef_server_url = "https://example.com/organizations/testorg" + EOH + it { is_expected.to eq <<~EOH.delete("#") } + Profile Client Key Server # + ----------------------------------------------------------------------------------# + *default testuser ~/.chef/testkey.pem https://example.com/organizations/testorg# + prod testuser ~/.chef/testkey.pem https://example.com/organizations/prod # + qa qauser ~/src/qauser.pem https://example.com/organizations/testorg# + EOH + end + + context "with a non-default active profile" do + let(:cmd_args) { %w{--profile prod} } + before { file(".chef/credentials", <<~EOH) } + [default] + client_name = "testuser" + client_key = "testkey.pem" + chef_server_url = "https://example.com/organizations/testorg" + + [prod] + client_name = "testuser" + client_key = "testkey.pem" + chef_server_url = "https://example.com/organizations/prod" + + [qa] + client_name = "qauser" + client_key = "~/src/qauser.pem" + chef_server_url = "https://example.com/organizations/testorg" + EOH + it { is_expected.to eq <<~EOH.delete("#") } + Profile Client Key Server # + ----------------------------------------------------------------------------------# + default testuser ~/.chef/testkey.pem https://example.com/organizations/testorg# + *prod testuser ~/.chef/testkey.pem https://example.com/organizations/prod # + qa qauser ~/src/qauser.pem https://example.com/organizations/testorg# + EOH + end + + context "with a minimal profile" do + before { file(".chef/credentials", <<~EOH) } + [default] + chef_server_url = "https://example.com/organizations/testorg" + EOH + it { is_expected.to match %r{^*default .*? https://example.com/organizations/testorg$} } + end + + context "with -i" do + let(:cmd_args) { %w{-i} } + before { file(".chef/credentials", <<~EOH) } + [default] + chef_server_url = "https://example.com/organizations/testorg" + EOH + it { is_expected.to eq <<~EOH.delete("#") } + Profile Client Key Server # + ----------------------------------------------------------------# + *default https://example.com/organizations/testorg# + EOH + end + + context "with --format=json" do + let(:cmd_args) { %w{--format=json node_name} } + before { file(".chef/credentials", <<~EOH) } + [default] + client_name = "testuser" + client_key = "testkey.pem" + chef_server_url = "https://example.com/organizations/testorg" + + [prod] + client_name = "testuser" + client_key = "testkey.pem" + chef_server_url = "https://example.com/organizations/prod" + + [qa] + client_name = "qauser" + client_key = "~/src/qauser.pem" + chef_server_url = "https://example.com/organizations/testorg" + EOH + it { + expect(JSON.parse(subject)).to eq [ + { "profile" => "default", "active" => true, "client_name" => "testuser", "client_key" => path_to(".chef/testkey.pem"), "server_url" => "https://example.com/organizations/testorg" }, + { "profile" => "prod", "active" => false, "client_name" => "testuser", "client_key" => path_to(".chef/testkey.pem"), "server_url" => "https://example.com/organizations/prod" }, + { "profile" => "qa", "active" => false, "client_name" => "qauser", "client_key" => path_to("src/qauser.pem"), "server_url" => "https://example.com/organizations/testorg" }, + ] } + end +end diff --git a/spec/integration/knife/config_use_profile_spec.rb b/spec/integration/knife/config_use_profile_spec.rb new file mode 100644 index 0000000000..a021dbbe6f --- /dev/null +++ b/spec/integration/knife/config_use_profile_spec.rb @@ -0,0 +1,100 @@ +# +# Copyright 2018, Noah Kantrowitz +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +require "support/shared/integration/integration_helper" +require "support/shared/context/config" + +describe "knife config use-profile", :workstation do + include IntegrationSupport + include KnifeSupport + + include_context "default config options" + include_context "with a chef repo" + + let(:cmd_args) { [] } + + let(:knife_use_profile) do + knife("config", "use-profile", *cmd_args, instance_filter: lambda { |instance| + # Fake the failsafe check because this command doesn't actually process knife.rb. + $__KNIFE_INTEGRATION_FAILSAFE_CHECK << " ole" + }) + end + + subject { knife_use_profile.stdout } + + around do |ex| + # Store and reset the value of some env vars. + old_chef_home = ENV["CHEF_HOME"] + old_knife_home = ENV["KNIFE_HOME"] + old_home = ENV["HOME"] + old_wd = Dir.pwd + ChefConfig::PathHelper.per_tool_home_environment = "KNIFE_HOME" + # Clear these out because they are cached permanently. + ChefConfig::PathHelper.class_exec { remove_class_variable(:@@home_dir) } + Chef::Knife::ConfigUseProfile.reset_config_loader! + begin + ex.run + ensure + ENV["CHEF_HOME"] = old_chef_home + ENV["KNIFE_HOME"] = old_knife_home + ENV["HOME"] = old_home + Dir.chdir(old_wd) + ENV[ChefConfig.windows? ? "CD" : "PWD"] = Dir.pwd + ChefConfig::PathHelper.per_tool_home_environment = nil + end + end + + before do + # Always run from the temp folder. This can't be in the `around` block above + # because it has to run after the before set in the "with a chef repo" shared context. + directory("repo") + Dir.chdir(path_to("repo")) + ENV[ChefConfig.windows? ? "CD" : "PWD"] = Dir.pwd + ENV["HOME"] = path_to(".") + end + + context "with no argument" do + subject { knife_use_profile.stderr } + it { is_expected.to eq "FATAL: You must specify a profile\n" } + end + + context "with an argument" do + let(:cmd_args) { %w{production} } + it do + is_expected.to eq "Set default profile to production\n" + expect(File.read(path_to(".chef/context"))).to eq "production\n" + end + end + + context "with $CHEF_HOME" do + let(:cmd_args) { %w{staging} } + before { ENV["CHEF_HOME"] = path_to("chefhome"); file("chefhome/tmp", "") } + it do + is_expected.to eq "Set default profile to staging\n" + expect(File.read(path_to("chefhome/.chef/context"))).to eq "staging\n" + expect(File.exist?(path_to(".chef/context"))).to be_falsey + end + end + + context "with $KNIFE_HOME" do + let(:cmd_args) { %w{development} } + before { ENV["KNIFE_HOME"] = path_to("knifehome"); file("knifehome/tmp", "") } + it do + is_expected.to eq "Set default profile to development\n" + expect(File.read(path_to("knifehome/.chef/context"))).to eq "development\n" + expect(File.exist?(path_to(".chef/context"))).to be_falsey + end + end +end |