spec/unit/knife/bootstrap_spec.rb in knife-18.2.7 vs spec/unit/knife/bootstrap_spec.rb in knife-18.3.0
- old
+ new
@@ -1,2233 +1,2288 @@
-#
-# Author:: Ian Meyer (<ianmmeyer@gmail.com>)
-# Copyright:: Copyright 2010-2016, Ian Meyer
-# Copyright:: Copyright (c) Chef Software Inc.
-# 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 "knife_spec_helper"
-
-Chef::Knife::Bootstrap.load_deps
-
-describe Chef::Knife::Bootstrap do
- let(:bootstrap_template) { nil }
- let(:stderr) { StringIO.new }
- let(:bootstrap_cli_options) { [ ] }
- let(:linux_test) { true }
- let(:windows_test) { false }
- let(:linux_test) { false }
- let(:unix_test) { false }
- let(:ssh_test) { false }
-
- let(:connection) do
- double("TrainConnector",
- windows?: windows_test,
- linux?: linux_test,
- unix?: unix_test)
- end
-
- let(:knife) do
- Chef::Log.logger = Logger.new(StringIO.new)
- Chef::Config[:knife][:bootstrap_template] = bootstrap_template unless bootstrap_template.nil?
-
- k = Chef::Knife::Bootstrap.new(bootstrap_cli_options)
- allow(k.ui).to receive(:stderr).and_return(stderr)
- allow(k).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(false)
- allow(k).to receive(:connection).and_return connection
- k.merge_configs
- k
- end
-
- context "#check_license" do
- let(:acceptor) { instance_double(LicenseAcceptance::Acceptor) }
-
- before do
- expect(LicenseAcceptance::Acceptor).to receive(:new).and_return(acceptor)
- end
-
- describe "when a license is not required" do
- it "does not set the chef_license" do
- expect(acceptor).to receive(:license_required?).and_return(false)
- knife.check_license
- expect(Chef::Config[:chef_license]).to eq(nil)
- end
- end
-
- describe "when a license is required" do
- it "sets the chef_license" do
- expect(acceptor).to receive(:license_required?).and_return(true)
- expect(acceptor).to receive(:id_from_mixlib).and_return("id")
- expect(acceptor).to receive(:check_and_persist)
- expect(acceptor).to receive(:acceptance_value).and_return("accept-no-persist")
- knife.check_license
- expect(Chef::Config[:chef_license]).to eq("accept-no-persist")
- end
- end
- end
-
- context "#bootstrap_template" do
- it "should default to chef-full" do
- expect(knife.bootstrap_template).to be_a_kind_of(String)
- expect(File.basename(knife.bootstrap_template)).to eq("chef-full")
- end
- end
-
- context "#render_template - when using the chef-full default template" do
- let(:rendered_template) do
- knife.merge_configs
- knife.render_template
- end
-
- it "should render client.rb" do
- expect(rendered_template).to match("cat > /etc/chef/client.rb <<'EOP'")
- expect(rendered_template).to match("chef_server_url \"https://localhost:443\"")
- expect(rendered_template).to match("validation_client_name \"chef-validator\"")
- expect(rendered_template).to match("log_location STDOUT")
- end
-
- it "should render first-boot.json" do
- expect(rendered_template).to match("cat > /etc/chef/first-boot.json <<'EOP'")
- expect(rendered_template).to match('{"run_list":\[\]}')
- end
-
- context "and encrypted_data_bag_secret was provided" do
- it "should render encrypted_data_bag_secret file" do
- expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(true)
- expect(knife).to receive(:read_secret).and_return("secrets")
- expect(rendered_template).to match("cat > /etc/chef/encrypted_data_bag_secret <<'EOP'")
- expect(rendered_template).to match('{"run_list":\[\]}')
- expect(rendered_template).to match(/secrets/)
- end
- end
- end
-
- context "with --bootstrap-vault-item" do
- let(:bootstrap_cli_options) { [ "--bootstrap-vault-item", "vault1:item1", "--bootstrap-vault-item", "vault1:item2", "--bootstrap-vault-item", "vault2:item1" ] }
- it "sets the knife config cli option correctly" do
- expect(knife.config[:bootstrap_vault_item]).to eq({ "vault1" => %w{item1 item2}, "vault2" => ["item1"] })
- end
- end
-
- context "with --bootstrap-preinstall-command" do
- command = "while sudo fuser /var/lib/dpkg/lock >/dev/null 2>&1; do\n echo 'waiting for dpkg lock';\n sleep 1;\n done;"
- let(:bootstrap_cli_options) { [ "--bootstrap-preinstall-command", command ] }
- let(:rendered_template) do
- knife.merge_configs
- knife.render_template
- end
- it "configures the preinstall command in the bootstrap template correctly" do
- expect(rendered_template).to match(/command/)
- end
- end
-
- context "with --bootstrap-proxy" do
- let(:bootstrap_cli_options) { [ "--bootstrap-proxy", "1.1.1.1" ] }
- let(:rendered_template) do
- knife.merge_configs
- knife.render_template
- end
- it "configures the https_proxy environment variable in the bootstrap template correctly" do
- expect(rendered_template).to match(/https_proxy="1.1.1.1" export https_proxy/)
- end
- end
-
- context "with --bootstrap-no-proxy" do
- let(:bootstrap_cli_options) { [ "--bootstrap-no-proxy", "localserver" ] }
- let(:rendered_template) do
- knife.merge_configs
- knife.render_template
- end
- it "configures the https_proxy environment variable in the bootstrap template correctly" do
- expect(rendered_template).to match(/no_proxy="localserver" export no_proxy/)
- end
- end
-
- context "with :bootstrap_template and :template_file cli options" do
- let(:bootstrap_cli_options) { [ "--bootstrap-template", "my-template", "other-template" ] }
-
- it "should select bootstrap template" do
- expect(File.basename(knife.bootstrap_template)).to eq("my-template")
- end
- end
-
- context "when finding templates" do
- context "when :bootstrap_template config is set to a file" do
- context "that doesn't exist" do
- let(:bootstrap_template) { "/opt/blah/not/exists/template.erb" }
-
- it "raises an error" do
- expect { knife.find_template }.to raise_error(Errno::ENOENT)
- end
- end
-
- context "that exists" do
- let(:bootstrap_template) { File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "test.erb")) }
-
- it "loads the given file as the template" do
- expect(Chef::Log).to receive(:trace)
- expect(knife.find_template).to eq(File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "test.erb")))
- end
- end
- end
-
- context "when :bootstrap_template config is set to a template name" do
- let(:bootstrap_template) { "example" }
-
- let(:builtin_template_path) { File.expand_path(File.join(__dir__, "../../../lib/chef/knife/bootstrap/templates", "example.erb")) }
-
- let(:chef_config_dir_template_path) { "/knife/chef/config/bootstrap/example.erb" }
-
- let(:env_home_template_path) { "/env/home/.chef/bootstrap/example.erb" }
-
- let(:gem_files_template_path) { "/Users/schisamo/.rvm/gems/ruby-1.9.2-p180@chef-0.10/gems/knife-windows-0.5.4/lib/chef/knife/bootstrap/fake-bootstrap-template.erb" }
-
- def configure_chef_config_dir
- allow(Chef::Knife).to receive(:chef_config_dir).and_return("/knife/chef/config")
- end
-
- def configure_env_home
- allow(Chef::Util::PathHelper).to receive(:home).with(".chef", "bootstrap", "example.erb").and_yield(env_home_template_path)
- end
-
- def configure_gem_files
- allow(Gem).to receive(:find_files).and_return([ gem_files_template_path ])
- end
-
- before(:each) do
- expect(File).to receive(:exist?).with(bootstrap_template).and_return(false)
- end
-
- context "when file is available everywhere" do
- before do
- configure_chef_config_dir
- configure_env_home
- configure_gem_files
-
- expect(File).to receive(:exist?).with(builtin_template_path).and_return(true)
- end
-
- it "should load the template from built-in templates" do
- expect(knife.find_template).to eq(builtin_template_path)
- end
- end
-
- context "when file is available in chef_config_dir" do
- before do
- configure_chef_config_dir
- configure_env_home
- configure_gem_files
-
- expect(File).to receive(:exist?).with(builtin_template_path).and_return(false)
- expect(File).to receive(:exist?).with(chef_config_dir_template_path).and_return(true)
-
- it "should load the template from chef_config_dir" do
- knife.find_template.should eq(chef_config_dir_template_path)
- end
- end
- end
-
- context "when file is available in home directory" do
- before do
- configure_chef_config_dir
- configure_env_home
- configure_gem_files
-
- expect(File).to receive(:exist?).with(builtin_template_path).and_return(false)
- expect(File).to receive(:exist?).with(chef_config_dir_template_path).and_return(false)
- expect(File).to receive(:exist?).with(env_home_template_path).and_return(true)
- end
-
- it "should load the template from chef_config_dir" do
- expect(knife.find_template).to eq(env_home_template_path)
- end
- end
-
- context "when file is available in Gem files" do
- before do
- configure_chef_config_dir
- configure_env_home
- configure_gem_files
-
- expect(File).to receive(:exist?).with(builtin_template_path).and_return(false)
- expect(File).to receive(:exist?).with(chef_config_dir_template_path).and_return(false)
- expect(File).to receive(:exist?).with(env_home_template_path).and_return(false)
- expect(File).to receive(:exist?).with(gem_files_template_path).and_return(true)
- end
-
- it "should load the template from Gem files" do
- expect(knife.find_template).to eq(gem_files_template_path)
- end
- end
-
- context "when file is available in Gem files and home dir doesn't exist" do
- before do
- configure_chef_config_dir
- configure_gem_files
- allow(Chef::Util::PathHelper).to receive(:home).with(".chef", "bootstrap", "example.erb").and_return(nil)
-
- expect(File).to receive(:exist?).with(builtin_template_path).and_return(false)
- expect(File).to receive(:exist?).with(chef_config_dir_template_path).and_return(false)
- expect(File).to receive(:exist?).with(gem_files_template_path).and_return(true)
- end
-
- it "should load the template from Gem files" do
- expect(knife.find_template).to eq(gem_files_template_path)
- end
- end
- end
- end
-
- ["-t", "--bootstrap-template"].each do |t|
- context "when #{t} option is given in the command line" do
- it "sets the knife :bootstrap_template config" do
- knife.parse_options([t, "blahblah"])
- knife.merge_configs
- expect(knife.bootstrap_template).to eq("blahblah")
- end
- end
- end
-
- context "with run_list template" do
- let(:bootstrap_template) { File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "test.erb")) }
-
- it "should return an empty run_list" do
- expect(knife.render_template).to eq('{"run_list":[]}')
- end
-
- it "should have role[base] in the run_list" do
- knife.parse_options(["-r", "role[base]"])
- knife.merge_configs
- expect(knife.render_template).to eq('{"run_list":["role[base]"]}')
- end
-
- it "should have role[base] and recipe[cupcakes] in the run_list" do
- knife.parse_options(["-r", "role[base],recipe[cupcakes]"])
- knife.merge_configs
- expect(knife.render_template).to eq('{"run_list":["role[base]","recipe[cupcakes]"]}')
- end
-
- context "with bootstrap_attribute options" do
- let(:jsonfile) do
- file = Tempfile.new(["node", ".json"])
- File.open(file.path, "w") { |f| f.puts '{"foo":{"bar":"baz"}}' }
- file
- end
-
- it "should have foo => {bar => baz} in the first_boot from cli" do
- knife.parse_options(["-j", '{"foo":{"bar":"baz"}}'])
- knife.merge_configs
- expected_hash = FFI_Yajl::Parser.new.parse('{"foo":{"bar":"baz"},"run_list":[]}')
- actual_hash = FFI_Yajl::Parser.new.parse(knife.render_template)
- expect(actual_hash).to eq(expected_hash)
- end
-
- it "should have foo => {bar => baz} in the first_boot from file" do
- knife.parse_options(["--json-attribute-file", jsonfile.path])
- knife.merge_configs
- expected_hash = FFI_Yajl::Parser.new.parse('{"foo":{"bar":"baz"},"run_list":[]}')
- actual_hash = FFI_Yajl::Parser.new.parse(knife.render_template)
- expect(actual_hash).to eq(expected_hash)
- jsonfile.close
- end
-
- it "raises a Chef::Exceptions::BootstrapCommandInputError with the proper error message" do
- knife.parse_options(["-j", '{"foo":{"bar":"baz"}}'])
- knife.parse_options(["--json-attribute-file", jsonfile.path])
- knife.merge_configs
- allow(knife).to receive(:validate_name_args!)
- expect(knife).to receive(:check_license)
-
- expect { knife.run }.to raise_error(Chef::Exceptions::BootstrapCommandInputError)
- jsonfile.close
- end
- end
- end
-
- context "with hints template" do
- let(:bootstrap_template) { File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "test-hints.erb")) }
-
- it "should create a hint file when told to" do
- knife.parse_options(["--hint", "openstack"])
- knife.merge_configs
- expect(knife.render_template).to match(%r{/etc/chef/ohai/hints/openstack.json})
- end
-
- it "should populate a hint file with JSON when given a file to read" do
- allow(::File).to receive(:read).and_return('{ "foo" : "bar" }')
- knife.parse_options(["--hint", "openstack=hints/openstack.json"])
- knife.merge_configs
- expect(knife.render_template).to match(/\{\"foo\":\"bar\"\}/)
- end
- end
-
- describe "specifying no_proxy with various entries" do
- subject(:knife) do
- k = described_class.new
- Chef::Config[:knife][:bootstrap_template] = template_file
- allow(k).to receive(:connection).and_return connection
- k.parse_options(options)
- k.merge_configs
- k
- end
-
- let(:options) { ["--bootstrap-no-proxy", setting] }
-
- let(:template_file) { File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "no_proxy.erb")) }
-
- let(:rendered_template) do
- knife.render_template
- end
-
- context "via --bootstrap-no-proxy" do
- let(:setting) { "api.opscode.com" }
-
- it "renders the client.rb with a single FQDN no_proxy entry" do
- expect(rendered_template).to match(/.*no_proxy\s*"api.opscode.com".*/)
- end
- end
-
- context "via --bootstrap-no-proxy multiple" do
- let(:setting) { "api.opscode.com,172.16.10.*" }
-
- it "renders the client.rb with comma-separated FQDN and wildcard IP address no_proxy entries" do
- expect(rendered_template).to match(/.*no_proxy\s*"api.opscode.com,172.16.10.\*".*/)
- end
- end
-
- context "via --ssl-verify-mode none" do
- let(:options) { ["--node-ssl-verify-mode", "none"] }
-
- it "renders the client.rb with ssl_verify_mode set to :verify_none" do
- expect(rendered_template).to match(/ssl_verify_mode :verify_none/)
- end
- end
-
- context "via --node-ssl-verify-mode peer" do
- let(:options) { ["--node-ssl-verify-mode", "peer"] }
-
- it "renders the client.rb with ssl_verify_mode set to :verify_peer" do
- expect(rendered_template).to match(/ssl_verify_mode :verify_peer/)
- end
- end
-
- context "via --node-ssl-verify-mode all" do
- let(:options) { ["--node-ssl-verify-mode", "all"] }
-
- it "raises error" do
- expect { rendered_template }.to raise_error(RuntimeError)
- end
- end
-
- context "via --node-verify-api-cert" do
- let(:options) { ["--node-verify-api-cert"] }
-
- it "renders the client.rb with verify_api_cert set to true" do
- expect(rendered_template).to match(/verify_api_cert true/)
- end
- end
-
- context "via --no-node-verify-api-cert" do
- let(:options) { ["--no-node-verify-api-cert"] }
-
- it "renders the client.rb with verify_api_cert set to false" do
- expect(rendered_template).to match(/verify_api_cert false/)
- end
- end
- end
-
- describe "specifying the encrypted data bag secret key" do
- let(:secret) { "supersekret" }
- let(:options) { [] }
- let(:bootstrap_template) { File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "secret.erb")) }
- let(:rendered_template) do
- knife.parse_options(options)
- knife.merge_configs
- knife.render_template
- end
-
- it "creates a secret file" do
- expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(true)
- expect(knife).to receive(:read_secret).and_return(secret)
- expect(rendered_template).to match(/#{secret}/)
- end
-
- it "renders the client.rb with an encrypted_data_bag_secret entry" do
- expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(true)
- expect(knife).to receive(:read_secret).and_return(secret)
- expect(rendered_template).to match(%r{encrypted_data_bag_secret\s*"/etc/chef/encrypted_data_bag_secret"})
- end
-
- end
-
- describe "when transferring trusted certificates" do
- let(:rendered_template) do
- knife.merge_configs
- knife.render_template
- end
-
- before do
- Chef::Config[:trusted_certs_dir] = Chef::Util::PathHelper.cleanpath(File.join(CHEF_SPEC_DATA, "trusted_certs"))
- end
-
- it "creates /etc/chef/trusted_certs" do
- expect(rendered_template).to match(%r{mkdir -p /etc/chef/trusted_certs})
- end
-
- it "copies the certificates in the directory" do
- certificates = Dir[File.join(Chef::Config[:trusted_certs_dir], "*.{crt,pem}")]
-
- certificates.each do |cert|
- expect(rendered_template).to match(%r{cat > /etc/chef/trusted_certs/#{File.basename(cert)} <<'EOP'})
- end
- end
-
- it "doesn't create /etc/chef/trusted_certs if :trusted_certs_dir is empty" do
- Dir.mktmpdir do |dir|
- Chef::Config[:trusted_certs_dir] = dir
- expect(rendered_template).not_to match(%r{mkdir -p /etc/chef/trusted_certs})
- end
- end
- end
-
- context "when doing fips things" do
- let(:template_file) { File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "no_proxy.erb")) }
-
- before do
- Chef::Config[:knife][:bootstrap_template] = template_file
- knife.merge_configs
- end
-
- let(:rendered_template) do
- knife.render_template
- end
-
- context "when knife is in fips mode" do
- before do
- Chef::Config[:fips] = true
- end
-
- it "renders 'fips true'" do
- expect(rendered_template).to match("fips")
- end
- end
-
- context "when knife is not in fips mode" do
- before do
- # This is required because the chef-fips pipeline does
- # has a default value of true for fips
- Chef::Config[:fips] = false
- end
-
- it "does not render anything about fips" do
- expect(rendered_template).not_to match("fips")
- end
- end
- end
-
- describe "when transferring client.d" do
-
- let(:rendered_template) do
- knife.merge_configs
- knife.render_template
- end
-
- before do
- Chef::Config[:client_d_dir] = client_d_dir
- end
-
- context "when client_d_dir is nil" do
- let(:client_d_dir) { nil }
-
- it "does not create /etc/chef/client.d" do
- expect(rendered_template).not_to match(%r{mkdir -p /etc/chef/client\.d})
- end
- end
-
- context "when client_d_dir is set" do
- let(:client_d_dir) do
- Chef::Util::PathHelper.cleanpath(
- File.join(__dir__, "../../data/client.d_00")
- )
- end
-
- it "creates /etc/chef/client.d" do
- expect(rendered_template).to match("mkdir -p /etc/chef/client\.d")
- end
-
- context "a flat directory structure" do
- it "escapes single-quotes" do
- expect(rendered_template).to match("cat > /etc/chef/client.d/02-strings.rb <<'EOP'")
- expect(rendered_template).to match("something '\\\\''/foo/bar'\\\\''")
- end
-
- it "creates a file 00-foo.rb" do
- expect(rendered_template).to match("cat > /etc/chef/client.d/00-foo.rb <<'EOP'")
- expect(rendered_template).to match("d6f9b976-289c-4149-baf7-81e6ffecf228")
- end
- it "creates a file bar" do
- expect(rendered_template).to match("cat > /etc/chef/client.d/bar <<'EOP'")
- expect(rendered_template).to match("1 / 0")
- end
- end
-
- context "a nested directory structure" do
- let(:client_d_dir) do
- Chef::Util::PathHelper.cleanpath(
- File.join(__dir__, "../../data/client.d_01")
- )
- end
- it "creates a file foo/bar.rb" do
- expect(rendered_template).to match("cat > /etc/chef/client.d/foo/bar.rb <<'EOP'")
- expect(rendered_template).to match("1 / 0")
- end
- end
- end
- end
-
- describe "#connection_protocol" do
- let(:host_descriptor) { "example.com" }
- let(:config) { {} }
- let(:knife_connection_protocol) { nil }
- before do
- allow(knife).to receive(:config).and_return config
- allow(knife).to receive(:host_descriptor).and_return host_descriptor
- if knife_connection_protocol
- Chef::Config[:knife][:connection_protocol] = knife_connection_protocol
- knife.merge_configs
- end
- end
-
- context "when protocol is part of the host argument" do
- let(:host_descriptor) { "winrm://myhost" }
-
- it "returns the value provided by the host argument" do
- expect(knife.connection_protocol).to eq "winrm"
- end
- end
-
- context "when protocol is provided via the CLI flag" do
- let(:config) { { connection_protocol: "winrm" } }
- it "returns that value" do
- expect(knife.connection_protocol).to eq "winrm"
- end
-
- end
- context "when protocol is provided via the host argument and the CLI flag" do
- let(:host_descriptor) { "ssh://example.com" }
- let(:config) { { connection_protocol: "winrm" } }
-
- it "returns the value provided by the host argument" do
- expect(knife.connection_protocol).to eq "ssh"
- end
- end
-
- context "when no explicit protocol is provided" do
- let(:config) { {} }
- let(:host_descriptor) { "example.com" }
- let(:knife_connection_protocol) { "winrm" }
- it "falls back to knife config" do
- expect(knife.connection_protocol).to eq "winrm"
- end
- context "and there is no knife bootstrap_protocol" do
- let(:knife_connection_protocol) { nil }
- it "falls back to 'ssh'" do
- expect(knife.connection_protocol).to eq "ssh"
- end
- end
- end
-
- end
-
- describe "#validate_protocol!" do
- let(:host_descriptor) { "example.com" }
- let(:config) { {} }
- let(:connection_protocol) { "ssh" }
- before do
- allow(knife).to receive(:config).and_return config
- allow(knife).to receive(:connection_protocol).and_return connection_protocol
- allow(knife).to receive(:host_descriptor).and_return host_descriptor
- end
-
- context "when protocol is provided both in the URL and via --protocol" do
-
- context "and they do not match" do
- let(:connection_protocol) { "ssh" }
- let(:config) { { connection_protocol: "winrm" } }
- it "outputs an error and exits" do
- expect(knife.ui).to receive(:error)
- expect { knife.validate_protocol! }.to raise_error SystemExit
- end
- end
-
- context "and they do match" do
- let(:connection_protocol) { "winrm" }
- let(:config) { { connection_protocol: "winrm" } }
- it "returns true" do
- expect(knife.validate_protocol!).to eq true
- end
- end
- end
-
- context "and the protocol is supported" do
-
- Chef::Knife::Bootstrap::SUPPORTED_CONNECTION_PROTOCOLS.each do |proto|
- let(:connection_protocol) { proto }
- it "returns true for #{proto}" do
- expect(knife.validate_protocol!).to eq true
- end
- end
- end
-
- context "and the protocol is not supported" do
- let(:connection_protocol) { "invalid" }
- it "outputs an error and exits" do
- expect(knife.ui).to receive(:error).with(/Unsupported protocol '#{connection_protocol}'/)
- expect { knife.validate_protocol! }.to raise_error SystemExit
- end
- end
- end
-
- describe "#validate_policy_options!" do
-
- context "when only policy_name is given" do
-
- let(:bootstrap_cli_options) { %w{ --policy-name my-app-server } }
-
- it "returns an error stating that policy_name and policy_group must be given together" do
- expect { knife.validate_policy_options! }.to raise_error(SystemExit)
- expect(stderr.string).to include("ERROR: --policy-name and --policy-group must be specified together")
- end
-
- end
-
- context "when only policy_group is given" do
-
- let(:bootstrap_cli_options) { %w{ --policy-group staging } }
-
- it "returns an error stating that policy_name and policy_group must be given together" do
- expect { knife.validate_policy_options! }.to raise_error(SystemExit)
- expect(stderr.string).to include("ERROR: --policy-name and --policy-group must be specified together")
- end
-
- end
-
- context "when both policy_name and policy_group are given, but run list is also given" do
-
- let(:bootstrap_cli_options) { %w{ --policy-name my-app --policy-group staging --run-list cookbook } }
-
- it "returns an error stating that policyfile and run_list are exclusive" do
- expect { knife.validate_policy_options! }.to raise_error(SystemExit)
- expect(stderr.string).to include("ERROR: Policyfile options and --run-list are exclusive")
- end
-
- end
-
- context "when policy_name and policy_group are given with no conflicting options" do
-
- let(:bootstrap_cli_options) { %w{ --policy-name my-app --policy-group staging } }
-
- it "passes options validation" do
- expect { knife.validate_policy_options! }.to_not raise_error
- end
-
- it "passes them into the bootstrap context" do
- expect(knife.bootstrap_context.first_boot).to have_key(:policy_name)
- expect(knife.bootstrap_context.first_boot).to have_key(:policy_group)
- end
-
- it "ensures that run_list is not set in the bootstrap context" do
- expect(knife.bootstrap_context.first_boot).to_not have_key(:run_list)
- end
-
- end
-
- # https://github.com/chef/chef/issues/4131
- # Arguably a bug in the plugin: it shouldn't be setting this to nil, but it
- # worked before, so make it work now.
- context "when a plugin sets the run list option to nil" do
- before do
- knife.config[:run_list] = nil
- end
-
- it "passes options validation" do
- expect { knife.validate_policy_options! }.to_not raise_error
- end
- end
- end
-
- # TODO - this is the only cli option we validate the _option_ itself -
- # so we'll know if someone accidentally deletes or renames use_sudo_password
- # Is this worht keeping? If so, then it seems we should expand it
- # to cover all options.
- context "validating use_sudo_password option" do
- it "use_sudo_password contains description and long params for help" do
- expect(knife.options).to(have_key(:use_sudo_password)) \
- && expect(knife.options[:use_sudo_password][:description].to_s).not_to(eq(""))\
- && expect(knife.options[:use_sudo_password][:long].to_s).not_to(eq(""))
- end
- end
-
- context "#connection_opts" do
- let(:connection_protocol) { "ssh" }
- before do
- allow(knife).to receive(:connection_protocol).and_return connection_protocol
- end
- context "behavioral test: " do
- let(:expected_connection_opts) do
- { base_opts: true,
- ssh_identity_opts: true,
- ssh_opts: true,
- gateway_opts: true,
- host_verify_opts: true,
- sudo_opts: true,
- winrm_opts: true }
- end
-
- it "queries and merges only expected configurations" do
- expect(knife).to receive(:base_opts).and_return({ base_opts: true })
- expect(knife).to receive(:host_verify_opts).and_return({ host_verify_opts: true })
- expect(knife).to receive(:gateway_opts).and_return({ gateway_opts: true })
- expect(knife).to receive(:sudo_opts).and_return({ sudo_opts: true })
- expect(knife).to receive(:winrm_opts).and_return({ winrm_opts: true })
- expect(knife).to receive(:ssh_opts).and_return({ ssh_opts: true })
- expect(knife).to receive(:ssh_identity_opts).and_return({ ssh_identity_opts: true })
- expect(knife.connection_opts).to match expected_connection_opts
- end
- end
-
- context "functional test: " do
- context "when protocol is winrm" do
- let(:connection_protocol) { "winrm" }
- # context "and neither CLI nor Chef::Config config entries have been provided"
- # end
- context "and all supported values are provided as Chef::Config entries" do
- before do
- # Set everything to easily identifiable and obviously fake values
- # to verify that Chef::Config is being sourced instead of knife.config
- knife.config = {}
- Chef::Config[:knife][:max_wait] = 9999.0
- Chef::Config[:knife][:winrm_user] = "winbob"
- Chef::Config[:knife][:winrm_port] = 9999
- Chef::Config[:knife][:ca_trust_file] = "trust.me"
- Chef::Config[:knife][:kerberos_realm] = "realm"
- Chef::Config[:knife][:kerberos_service] = "service"
- Chef::Config[:knife][:winrm_auth_method] = "kerberos" # default is negotiate
- Chef::Config[:knife][:winrm_basic_auth_only] = true
- Chef::Config[:knife][:winrm_no_verify_cert] = true
- Chef::Config[:knife][:session_timeout] = 9999
- Chef::Config[:knife][:winrm_ssl] = true
- Chef::Config[:knife][:winrm_ssl_peer_fingerprint] = "ABCDEF"
- end
-
- context "and no CLI options have been given" do
- let(:expected_result) do
- {
- logger: Chef::Log, # not configurable
- ca_trust_path: "trust.me",
- max_wait_until_ready: 9999, # converted to int
- operation_timeout: 9999,
- ssl_peer_fingerprint: "ABCDEF",
- winrm_transport: "kerberos",
- winrm_basic_auth_only: true,
- user: "winbob",
- port: 9999,
- self_signed: true,
- ssl: true,
- kerberos_realm: "realm",
- kerberos_service: "service",
- }
- end
-
- it "generates a config hash using the Chef::Config values" do
- knife.merge_configs
- expect(knife.connection_opts).to match expected_result
- end
-
- end
-
- context "and some CLI options have been given" do
- let(:expected_result) do
- {
- logger: Chef::Log, # not configurable
- ca_trust_path: "no trust",
- max_wait_until_ready: 9999,
- operation_timeout: 9999,
- ssl_peer_fingerprint: "ABCDEF",
- winrm_transport: "kerberos",
- winrm_basic_auth_only: true,
- user: "microsoftbob",
- port: 12,
- self_signed: true,
- ssl: true,
- kerberos_realm: "realm",
- kerberos_service: "service",
- password: "lobster",
- }
- end
-
- before do
- knife.config[:ca_trust_file] = "no trust"
- knife.config[:connection_user] = "microsoftbob"
- knife.config[:connection_port] = 12
- knife.config[:winrm_port] = "13" # indirectly verify we're not looking for the wrong CLI flag
- knife.config[:connection_password] = "lobster"
- end
-
- it "generates a config hash using the CLI options when available and falling back to Chef::Config values" do
- knife.merge_configs
- expect(knife.connection_opts).to match expected_result
- end
- end
-
- context "and all CLI options have been given" do
- before do
- # We'll force kerberos vi knife.config because it
- # causes additional options to populate - make sure
- # Chef::Config is different so we can be sure that we didn't
- # pull in the Chef::Config value
- Chef::Config[:knife][:winrm_auth_method] = "negotiate"
- knife.config[:connection_password] = "blue"
- knife.config[:max_wait] = 1000.0
- knife.config[:connection_user] = "clippy"
- knife.config[:connection_port] = 1000
- knife.config[:winrm_port] = 1001 # We should not see this value get used
-
- knife.config[:ca_trust_file] = "trust.the.internet"
- knife.config[:kerberos_realm] = "otherrealm"
- knife.config[:kerberos_service] = "otherservice"
- knife.config[:winrm_auth_method] = "kerberos" # default is negotiate
- knife.config[:winrm_basic_auth_only] = false
- knife.config[:winrm_no_verify_cert] = false
- knife.config[:session_timeout] = 1000
- knife.config[:winrm_ssl] = false
- knife.config[:winrm_ssl_peer_fingerprint] = "FEDCBA"
- end
- let(:expected_result) do
- {
- logger: Chef::Log, # not configurable
- ca_trust_path: "trust.the.internet",
- max_wait_until_ready: 1000, # converted to int
- operation_timeout: 1000,
- ssl_peer_fingerprint: "FEDCBA",
- winrm_transport: "kerberos",
- winrm_basic_auth_only: false,
- user: "clippy",
- port: 1000,
- self_signed: false,
- ssl: false,
- kerberos_realm: "otherrealm",
- kerberos_service: "otherservice",
- password: "blue",
- }
- end
- it "generates a config hash using the CLI options and pulling nothing from Chef::Config" do
- knife.merge_configs
- expect(knife.connection_opts).to match expected_result
- end
- end
- end # with underlying Chef::Config values
-
- context "and no values are provided from Chef::Config or CLI" do
- before do
- # We will use knife's actual config since these tests
- # have assumptions based on CLI default values
- end
- let(:expected_result) do
- {
- logger: Chef::Log,
- operation_timeout: 60,
- self_signed: false,
- ssl: false,
- ssl_peer_fingerprint: nil,
- winrm_basic_auth_only: false,
- winrm_transport: "negotiate",
- }
- end
- it "populates appropriate defaults" do
- knife.merge_configs
- expect(knife.connection_opts).to match expected_result
- end
- end
- end # winrm
-
- context "when protocol is ssh" do
- let(:connection_protocol) { "ssh" }
- # context "and neither CLI nor Chef::Config config entries have been provided"
- # end
- context "and all supported values are provided as Chef::Config entries" do
- before do
- # Set everything to easily identifiable and obviously fake values
- # to verify that Chef::Config is being sourced instead of knife.config
- knife.config = {}
- Chef::Config[:knife][:max_wait] = 9999.0
- Chef::Config[:knife][:session_timeout] = 9999
- Chef::Config[:knife][:ssh_user] = "sshbob"
- Chef::Config[:knife][:ssh_port] = 9999
- Chef::Config[:knife][:host_key_verify] = false
- Chef::Config[:knife][:ssh_gateway_identity] = "/gateway.pem"
- Chef::Config[:knife][:ssh_gateway] = "admin@mygateway.local:1234"
- Chef::Config[:knife][:ssh_identity_file] = "/identity.pem"
- Chef::Config[:knife][:use_sudo_password] = false # We have no password.
- end
-
- context "and no CLI options have been given" do
- let(:expected_result) do
- {
- logger: Chef::Log, # not configurable
- max_wait_until_ready: 9999, # converted to int
- connection_timeout: 9999,
- user: "sshbob",
- bastion_host: "mygateway.local",
- bastion_port: 1234,
- bastion_user: "admin",
- forward_agent: false,
- keys_only: true,
- key_files: ["/identity.pem", "/gateway.pem"],
- sudo: false,
- verify_host_key: "always",
- port: 9999,
- non_interactive: true,
- }
- end
-
- it "generates a correct config hash using the Chef::Config values" do
- knife.merge_configs
- expect(knife.connection_opts).to match expected_result
- end
- end
-
- context "and unsupported Chef::Config options are given in Chef::Config, not in CLI" do
- before do
- Chef::Config[:knife][:password] = "blah"
- Chef::Config[:knife][:ssh_password] = "blah"
- Chef::Config[:knife][:preserve_home] = true
- Chef::Config[:knife][:use_sudo] = true
- Chef::Config[:knife][:ssh_forward_agent] = "blah"
- end
- it "does not include the corresponding option in the connection options" do
- knife.merge_configs
- expect(knife.connection_opts.key?(:password)).to eq false
- expect(knife.connection_opts.key?(:ssh_forward_agent)).to eq false
- expect(knife.connection_opts.key?(:use_sudo)).to eq false
- expect(knife.connection_opts.key?(:preserve_home)).to eq false
- end
- end
-
- context "and some CLI options have been given" do
- before do
- knife.config = {}
- knife.config[:connection_user] = "sshalice"
- knife.config[:connection_port] = 12
- knife.config[:ssh_port] = "13" # canary to indirectly verify we're not looking for the wrong CLI flag
- knife.config[:connection_password] = "feta cheese"
- knife.config[:max_wait] = 150.0
- knife.config[:session_timeout] = 120
- knife.config[:use_sudo] = true
- knife.config[:use_sudo_pasword] = true
- knife.config[:ssh_forward_agent] = true
- end
-
- let(:expected_result) do
- {
- logger: Chef::Log, # not configurable
- max_wait_until_ready: 150, # cli (converted to int)
- connection_timeout: 120, # cli
- user: "sshalice", # cli
- password: "feta cheese", # cli
- bastion_host: "mygateway.local", # Config
- bastion_port: 1234, # Config
- bastion_user: "admin", # Config
- forward_agent: true, # cli
- keys_only: false, # implied false from config password present
- key_files: ["/identity.pem", "/gateway.pem"], # Config
- sudo: true, # ccli
- verify_host_key: "always", # Config
- port: 12, # cli
- non_interactive: true,
- }
- end
-
- it "generates a config hash using the CLI options when available and falling back to Chef::Config values" do
- knife.merge_configs
- expect(knife.connection_opts).to match expected_result
- end
- end
-
- context "and all CLI options have been given" do
- before do
- knife.config = {}
- knife.config[:max_wait] = 150.0
- knife.config[:session_timeout] = 120
- knife.config[:connection_user] = "sshroot"
- knife.config[:connection_port] = 1000
- knife.config[:connection_password] = "blah"
- knife.config[:forward_agent] = true
- knife.config[:use_sudo] = true
- knife.config[:use_sudo_password] = true
- knife.config[:preserve_home] = true
- knife.config[:use_sudo_pasword] = true
- knife.config[:ssh_forward_agent] = true
- knife.config[:ssh_verify_host_key] = true
- knife.config[:ssh_gateway_identity] = "/gateway-identity.pem"
- knife.config[:ssh_gateway] = "me@example.com:10"
- knife.config[:ssh_identity_file] = "/my-identity.pem"
-
- # We'll set these as canaries - if one of these values shows up
- # in a failed test, then the behavior of not pulling from these keys
- # out of knife.config is broken:
- knife.config[:ssh_user] = "do not use"
- knife.config[:ssh_port] = 1001
- end
- let(:expected_result) do
- {
- logger: Chef::Log, # not configurable
- max_wait_until_ready: 150, # converted to int
- connection_timeout: 120,
- user: "sshroot",
- password: "blah",
- port: 1000,
- bastion_host: "example.com",
- bastion_port: 10,
- bastion_user: "me",
- forward_agent: true,
- keys_only: false,
- key_files: ["/my-identity.pem", "/gateway-identity.pem"],
- sudo: true,
- sudo_options: "-H",
- sudo_password: "blah",
- verify_host_key: true,
- non_interactive: true,
- }
- end
- it "generates a config hash using the CLI options and pulling nothing from Chef::Config" do
- knife.merge_configs
- expect(knife.connection_opts).to match expected_result
- end
- end
- end
- context "and no values are provided from Chef::Config or CLI" do
- before do
- # We will use knife's actual config since these tests
- # have assumptions based on CLI default values
- config = {}
- end
-
- let(:expected_result) do
- {
- forward_agent: false,
- key_files: [],
- logger: Chef::Log,
- keys_only: false,
- sudo: false,
- verify_host_key: "always",
- non_interactive: true,
- connection_timeout: 60,
- }
- end
- it "populates appropriate defaults" do
- knife.merge_configs
- expect(knife.connection_opts).to match expected_result
- end
- end
-
- end # ssh
- end # functional tests
-
- end # connection_opts
-
- context "#base_opts" do
- let(:connection_protocol) { nil }
-
- before do
- allow(knife).to receive(:connection_protocol).and_return connection_protocol
- end
-
- context "for all protocols" do
- context "when password is provided" do
- before do
- knife.config[:connection_port] = 250
- knife.config[:connection_user] = "test"
- knife.config[:connection_password] = "opscode"
- end
-
- let(:expected_opts) do
- {
- port: 250,
- user: "test",
- logger: Chef::Log,
- password: "opscode",
- }
- end
- it "generates the correct options" do
- expect(knife.base_opts).to eq expected_opts
- end
-
- end
-
- context "when password is not provided" do
- before do
- knife.config[:connection_port] = 250
- knife.config[:connection_user] = "test"
- end
-
- let(:expected_opts) do
- {
- port: 250,
- user: "test",
- logger: Chef::Log,
- }
- end
- it "generates the correct options" do
- expect(knife.base_opts).to eq expected_opts
- end
- end
- end
- end
-
- context "#host_verify_opts" do
- let(:connection_protocol) { nil }
- before do
- allow(knife).to receive(:connection_protocol).and_return connection_protocol
- end
-
- context "for winrm" do
- let(:connection_protocol) { "winrm" }
- it "returns the expected configuration" do
- knife.config[:winrm_no_verify_cert] = true
- expect(knife.host_verify_opts).to eq( { self_signed: true } )
- end
- it "provides a correct default when no option given" do
- expect(knife.host_verify_opts).to eq( { self_signed: false } )
- end
- end
-
- context "for ssh" do
- let(:connection_protocol) { "ssh" }
- it "returns the expected configuration" do
- knife.config[:ssh_verify_host_key] = false
- expect(knife.host_verify_opts).to eq( { verify_host_key: false } )
- end
- it "provides a correct default when no option given" do
- expect(knife.host_verify_opts).to eq( { verify_host_key: "always" } )
- end
- end
- end
-
- # TODO - test keys_only, password, config source behavior
- context "#ssh_identity_opts" do
- let(:connection_protocol) { nil }
- before do
- allow(knife).to receive(:connection_protocol).and_return connection_protocol
- end
-
- context "for winrm" do
- let(:connection_protocol) { "winrm" }
- it "returns an empty hash" do
- expect(knife.ssh_identity_opts).to eq({})
- end
- end
-
- context "for ssh" do
- let(:connection_protocol) { "ssh" }
- context "when an identity file is specified" do
- before do
- knife.config[:ssh_identity_file] = "/identity.pem"
- end
- it "generates the expected configuration" do
- expect(knife.ssh_identity_opts).to eq({
- key_files: [ "/identity.pem" ],
- keys_only: true,
- })
- end
- context "and a password is also specified" do
- before do
- knife.config[:connection_password] = "blah"
- end
- it "generates the expected configuration (key, keys_only false)" do
- expect(knife.ssh_identity_opts).to eq({
- key_files: [ "/identity.pem" ],
- keys_only: false,
- })
- end
- end
-
- context "and a gateway is not specified" do
- context "but a gateway identity file is specified" do
- it "does not include the gateway identity file in keys" do
- expect(knife.ssh_identity_opts).to eq({
- key_files: ["/identity.pem"],
- keys_only: true,
- })
- end
-
- end
-
- end
-
- context "and a gatway is specified" do
- before do
- knife.config[:ssh_gateway] = "example.com"
- end
- context "and a gateway identity file is not specified" do
- it "config includes only identity file and not gateway identity" do
- expect(knife.ssh_identity_opts).to eq({
- key_files: [ "/identity.pem" ],
- keys_only: true,
- })
- end
- end
-
- context "and a gateway identity file is also specified" do
- before do
- knife.config[:ssh_gateway_identity] = "/gateway.pem"
- end
-
- it "generates the expected configuration (both keys, keys_only true)" do
- expect(knife.ssh_identity_opts).to eq({
- key_files: [ "/identity.pem", "/gateway.pem" ],
- keys_only: true,
- })
- end
- end
- end
- end
-
- context "when no identity file is specified" do
- it "generates the expected configuration (no keys, keys_only false)" do
- expect(knife.ssh_identity_opts).to eq( {
- key_files: [],
- keys_only: false,
- })
- end
- context "and a gateway with gateway identity file is specified" do
- before do
- knife.config[:ssh_gateway] = "host"
- knife.config[:ssh_gateway_identity] = "/gateway.pem"
- end
-
- it "generates the expected configuration (gateway key, keys_only false)" do
- expect(knife.ssh_identity_opts).to eq({
- key_files: [ "/gateway.pem" ],
- keys_only: false,
- })
- end
- end
- end
- end
- end
-
- context "#gateway_opts" do
- let(:connection_protocol) { nil }
- before do
- allow(knife).to receive(:connection_protocol).and_return connection_protocol
- end
-
- context "for winrm" do
- let(:connection_protocol) { "winrm" }
- it "returns an empty hash" do
- expect(knife.gateway_opts).to eq({})
- end
- end
-
- context "for ssh" do
- let(:connection_protocol) { "ssh" }
- context "and ssh_gateway with hostname, user and port provided" do
- before do
- knife.config[:ssh_gateway] = "testuser@gateway:9021"
- end
- it "returns a proper bastion host config subset" do
- expect(knife.gateway_opts).to eq({
- bastion_user: "testuser",
- bastion_host: "gateway",
- bastion_port: 9021,
- })
- end
- end
- context "and ssh_gateway with only hostname is given" do
- before do
- knife.config[:ssh_gateway] = "gateway"
- end
- it "returns a proper bastion host config subset" do
- expect(knife.gateway_opts).to eq({
- bastion_user: nil,
- bastion_host: "gateway",
- bastion_port: nil,
- })
- end
- end
- context "and ssh_gateway with hostname and user is is given" do
- before do
- knife.config[:ssh_gateway] = "testuser@gateway"
- end
- it "returns a proper bastion host config subset" do
- expect(knife.gateway_opts).to eq({
- bastion_user: "testuser",
- bastion_host: "gateway",
- bastion_port: nil,
- })
- end
- end
-
- context "and ssh_gateway with hostname and port is is given" do
- before do
- knife.config[:ssh_gateway] = "gateway:11234"
- end
- it "returns a proper bastion host config subset" do
- expect(knife.gateway_opts).to eq({
- bastion_user: nil,
- bastion_host: "gateway",
- bastion_port: 11234,
- })
- end
- end
-
- context "and ssh_gateway is not provided" do
- it "returns an empty hash" do
- expect(knife.gateway_opts).to eq({})
- end
- end
- end
- end
-
- context "#sudo_opts" do
- let(:connection_protocol) { nil }
- before do
- allow(knife).to receive(:connection_protocol).and_return connection_protocol
- end
-
- context "for winrm" do
- let(:connection_protocol) { "winrm" }
- it "returns an empty hash" do
- expect(knife.sudo_opts).to eq({})
- end
- end
-
- context "for ssh" do
- let(:connection_protocol) { "ssh" }
- context "when use_sudo is set" do
- before do
- knife.config[:use_sudo] = true
- end
-
- it "returns a config that enables sudo" do
- expect(knife.sudo_opts).to eq( { sudo: true } )
- end
-
- context "when use_sudo_password is also set" do
- before do
- knife.config[:use_sudo_password] = true
- knife.config[:connection_password] = "opscode"
- end
- it "includes :connection_password value in a sudo-enabled configuration" do
- expect(knife.sudo_opts).to eq({
- sudo: true,
- sudo_password: "opscode",
- })
- end
- end
-
- context "when preserve_home is set" do
- before do
- knife.config[:preserve_home] = true
- end
- it "enables sudo with sudo_option to preserve home" do
- expect(knife.sudo_opts).to eq({
- sudo_options: "-H",
- sudo: true,
- })
- end
- end
- end
-
- context "when use_sudo is not set" do
- before do
- knife.config[:use_sudo_password] = true
- knife.config[:preserve_home] = true
- end
- it "returns configuration for sudo off, ignoring other related options" do
- expect(knife.sudo_opts).to eq( { sudo: false } )
- end
- end
- end
- end
-
- context "#ssh_opts" do
- let(:connection_protocol) { nil }
- before do
- allow(knife).to receive(:connection_protocol).and_return connection_protocol
- end
-
- context "for ssh" do
- let(:connection_protocol) { "ssh" }
- let(:default_opts) do
- {
- non_interactive: true,
- forward_agent: false,
- connection_timeout: 60,
- }
- end
-
- context "by default" do
- it "returns a configuration hash with appropriate defaults" do
- expect(knife.ssh_opts).to eq default_opts
- end
- end
-
- context "when ssh_forward_agent has a value" do
- before do
- knife.config[:ssh_forward_agent] = true
- end
- it "returns a default configuration hash with forward_agent set to true" do
- expect(knife.ssh_opts).to eq(default_opts.merge(forward_agent: true))
- end
- end
- context "when session_timeout has a value" do
- before do
- knife.config[:session_timeout] = 120
- end
- it "returns a default configuration hash with updated timeout value." do
- expect(knife.ssh_opts).to eq(default_opts.merge(connection_timeout: 120))
- end
- end
-
- end
-
- context "for winrm" do
- let(:connection_protocol) { "winrm" }
- it "returns an empty has because ssh is not winrm" do
- expect(knife.ssh_opts).to eq({})
- end
- end
-
- end
-
- context "#winrm_opts" do
- let(:connection_protocol) { nil }
- before do
- allow(knife).to receive(:connection_protocol).and_return connection_protocol
- end
-
- context "for winrm" do
- let(:connection_protocol) { "winrm" }
- let(:expected) do
- {
- winrm_transport: "negotiate",
- winrm_basic_auth_only: false,
- ssl: false,
- ssl_peer_fingerprint: nil,
- operation_timeout: 60,
- }
- end
-
- it "generates a correct configuration hash with expected defaults" do
- expect(knife.winrm_opts).to eq expected
- end
-
- context "with ssl_peer_fingerprint" do
- let(:ssl_peer_fingerprint_expected) do
- expected.merge({ ssl_peer_fingerprint: "ABCD" })
- end
-
- before do
- knife.config[:winrm_ssl_peer_fingerprint] = "ABCD"
- end
-
- it "generates a correct options hash with ssl_peer_fingerprint from the config provided" do
- expect(knife.winrm_opts).to eq ssl_peer_fingerprint_expected
- end
- end
-
- context "with winrm_ssl" do
- let(:ssl_expected) do
- expected.merge({ ssl: true })
- end
- before do
- knife.config[:winrm_ssl] = true
- end
-
- it "generates a correct options hash with ssl from the config provided" do
- expect(knife.winrm_opts).to eq ssl_expected
- end
- end
-
- context "with winrm_auth_method" do
- let(:winrm_auth_method_expected) do
- expected.merge({ winrm_transport: "freeaccess" })
- end
-
- before do
- knife.config[:winrm_auth_method] = "freeaccess"
- end
-
- it "generates a correct options hash with winrm_transport from the config provided" do
- expect(knife.winrm_opts).to eq winrm_auth_method_expected
- end
- end
-
- context "with ca_trust_file" do
- let(:ca_trust_expected) do
- expected.merge({ ca_trust_path: "/trust.me" })
- end
- before do
- knife.config[:ca_trust_file] = "/trust.me"
- end
-
- it "generates a correct options hash with ca_trust_file from the config provided" do
- expect(knife.winrm_opts).to eq ca_trust_expected
- end
- end
-
- context "with kerberos auth" do
- let(:kerberos_expected) do
- expected.merge({
- kerberos_service: "testsvc",
- kerberos_realm: "TESTREALM",
- winrm_transport: "kerberos",
- })
- end
-
- before do
- knife.config[:winrm_auth_method] = "kerberos"
- knife.config[:kerberos_service] = "testsvc"
- knife.config[:kerberos_realm] = "TESTREALM"
- end
-
- it "generates a correct options hash containing kerberos auth configuration from the config provided" do
- expect(knife.winrm_opts).to eq kerberos_expected
- end
- end
-
- context "with winrm_basic_auth_only" do
- before do
- knife.config[:winrm_basic_auth_only] = true
- end
- let(:basic_auth_expected) do
- expected.merge( { winrm_basic_auth_only: true } )
- end
- it "generates a correct options hash containing winrm_basic_auth_only from the config provided" do
- expect(knife.winrm_opts).to eq basic_auth_expected
- end
- end
- end
-
- context "for ssh" do
- let(:connection_protocol) { "ssh" }
- it "returns an empty hash because ssh is not winrm" do
- expect(knife.winrm_opts).to eq({})
- end
- end
- end
- describe "#run" do
- it "performs the steps we expect to run a bootstrap" do
- expect(knife).to receive(:check_license)
- expect(knife).to receive(:validate_name_args!).ordered
- expect(knife).to receive(:validate_protocol!).ordered
- expect(knife).to receive(:validate_first_boot_attributes!).ordered
- expect(knife).to receive(:validate_winrm_transport_opts!).ordered
- expect(knife).to receive(:validate_policy_options!).ordered
- expect(knife).to receive(:winrm_warn_no_ssl_verification).ordered
- expect(knife).to receive(:warn_on_short_session_timeout).ordered
- expect(knife).to receive(:connect!).ordered
- expect(knife).to receive(:register_client).ordered
- expect(knife).to receive(:render_template).and_return "content"
- expect(knife).to receive(:upload_bootstrap).with("content").and_return "/remote/path.sh"
- expect(knife).to receive(:perform_bootstrap).with("/remote/path.sh")
- expect(connection).to receive(:del_file!) # Make sure cleanup happens
-
- knife.run
-
- # Post-run verify expected state changes (not many directly in #run)
- expect($stdout.sync).to eq true
- end
- end
-
- describe "#register_client" do
- let(:vault_handler_mock) { double("ChefVaultHandler") }
- let(:client_builder_mock) { double("ClientBuilder") }
- let(:node_name) { nil }
- before do
- allow(knife).to receive(:chef_vault_handler).and_return vault_handler_mock
- allow(knife).to receive(:client_builder).and_return client_builder_mock
- knife.config[:chef_node_name] = node_name
- end
-
- shared_examples_for "creating the client locally" do
- context "when a valid node name is present" do
- let(:node_name) { "test" }
- before do
- allow(client_builder_mock).to receive(:client).and_return "client"
- allow(client_builder_mock).to receive(:client_path).and_return "/key.pem"
- end
-
- it "runs client_builder and vault_handler" do
- expect(client_builder_mock).to receive(:run)
- expect(vault_handler_mock).to receive(:run).with("client")
- knife.register_client
- end
-
- it "sets the path to the client key in the bootstrap context" do
- allow(client_builder_mock).to receive(:run)
- allow(vault_handler_mock).to receive(:run).with("client")
- knife.register_client
- expect(knife.bootstrap_context.client_pem).to eq "/key.pem"
- end
- end
-
- context "when no valid node name is present" do
- let(:node_name) { nil }
- it "shows an error and exits" do
- expect(knife.ui).to receive(:error)
- expect { knife.register_client }.to raise_error(SystemExit)
- end
- end
- end
- context "when chef_vault_handler says we're using vault" do
- let(:vault_handler_mock) { double("ChefVaultHandler") }
- before do
- allow(vault_handler_mock).to receive(:doing_chef_vault?).and_return true
- end
- it_behaves_like "creating the client locally"
- end
-
- context "when an non-existant validation key is specified in chef config" do
- before do
- Chef::Config[:validation_key] = "/blah"
- allow(vault_handler_mock).to receive(:doing_chef_vault?).and_return false
- allow(File).to receive(:exist?).with(%r{/blah}).and_return false
- end
- it_behaves_like "creating the client locally"
- end
-
- context "when a valid validation key is given and we're doing old-style client creation" do
- before do
- Chef::Config[:validation_key] = "/blah"
- allow(File).to receive(:exist?).with(%r{/blah}).and_return true
- allow(vault_handler_mock).to receive(:doing_chef_vault?).and_return false
- end
-
- it "shows a warning message" do
- expect(knife.ui).to receive(:warn).twice
- knife.register_client
- end
- end
- end
-
- describe "#perform_bootstrap" do
- let(:exit_status) { 0 }
- let(:stdout) { "" }
- let(:result_mock) { double("result", exit_status: exit_status, stderr: "A message", stdout: stdout) }
-
- before do
- allow(connection).to receive(:hostname).and_return "testhost"
- end
- it "runs the remote script and logs the output" do
- expect(knife.ui).to receive(:info).with(/Bootstrapping.*/)
- expect(knife).to receive(:bootstrap_command)
- .with("/path.sh")
- .and_return("sh /path.sh")
- expect(connection)
- .to receive(:run_command)
- .with("sh /path.sh")
- .and_yield("output here", nil)
- .and_return result_mock
-
- expect(knife.ui).to receive(:msg).with(/testhost/)
- knife.perform_bootstrap("/path.sh")
- end
-
- context "when the remote command fails" do
- let(:exit_status) { 1 }
- it "shows an error and exits" do
- expect(knife.ui).to receive(:info).with(/Bootstrapping.*/)
- expect(knife).to receive(:bootstrap_command)
- .with("/path.sh")
- .and_return("sh /path.sh")
- expect(connection).to receive(:run_command).with("sh /path.sh").and_return result_mock
- expect { knife.perform_bootstrap("/path.sh") }.to raise_error(SystemExit)
- end
- end
-
- context "when the remote command failed due to su auth error" do
- let(:exit_status) { 1 }
- let(:stdout) { "su: Authentication failure" }
- let(:connection_obj) { double("connection", transport_options: {}) }
- it "shows an error and exits" do
- allow(connection).to receive(:connection).and_return(connection_obj)
- expect(knife.ui).to receive(:info).with(/Bootstrapping.*/)
- expect(knife).to receive(:bootstrap_command)
- .with("/path.sh")
- .and_return("su - USER -c 'sh /path.sh'")
- expect(connection)
- .to receive(:run_command)
- .with("su - USER -c 'sh /path.sh'")
- .and_yield("output here", nil)
- .and_raise(Train::UserError)
- expect { knife.perform_bootstrap("/path.sh") }.to raise_error(Train::UserError)
- end
- end
- end
-
- describe "#connect!" do
- before do
- # These are not required at run-time because train will handle its own
- # protocol loading. In this case, we're simulating train failures and have to load
- # them ourselves.
- require "net/ssh"
- require "train/transports/ssh"
- end
-
- context "in the normal case" do
- it "connects using the connection_opts and notifies the operator of progress" do
- expect(knife.ui).to receive(:info).with(/Connecting to.*/)
- expect(knife).to receive(:connection_opts).and_return( { opts: "here" })
- expect(knife).to receive(:do_connect).with( { opts: "here" } )
- knife.connect!
- end
- end
-
- context "when a general non-auth-failure occurs" do
- let(:expected_error) { RuntimeError.new }
- before do
- allow(knife).to receive(:do_connect).and_raise(expected_error)
- end
- it "re-raises the exception" do
- expect { knife.connect! }.to raise_error(expected_error)
- end
- end
-
- context "when ssh fingerprint is invalid" do
- let(:expected_error) { Train::Error.new("fingerprint AA:BB is unknown for \"blah,127.0.0.1\"") }
- before do
- allow(knife).to receive(:do_connect).and_raise(expected_error)
- end
- it "warns, prompts to accept, then connects with verify_host_key of accept_new" do
- expect(knife).to receive(:do_connect).and_raise(expected_error)
- expect(knife.ui).to receive(:confirm)
- .with(/.*host 'blah \(127.0.0.1\)'.*AA:BB.*Are you sure you want to continue.*/m)
- .and_return(true)
- expect(knife).to receive(:do_connect) do |opts|
- expect(opts[:verify_host_key]).to eq :accept_new
- end
- knife.connect!
- end
- end
-
- context "when an auth failure occurs" do
- let(:expected_error) do
- e = Train::Error.new
- actual = Net::SSH::AuthenticationFailed.new
- # Simulate train's nested error - they wrap
- # ssh/network errors in a TrainError.
- allow(e).to receive(:cause).and_return(actual)
- e
- end
-
- let(:expected_error_password_prompt) do
- e = Train::ClientError.new
- reason = :no_ssh_password_or_key_available
- allow(e).to receive(:reason).and_return(reason)
- e
- end
-
- let(:expected_error_password_prompt_winrm) do
- e = RuntimeError.new
- message = "password is a required option"
- allow(e).to receive(:message).and_return(message)
- e
- end
-
- context "and password auth was used" do
- before do
- allow(connection).to receive(:password_auth?).and_return true
- end
-
- it "re-raises the error so as not to resubmit the same failing password" do
- expect(knife).to receive(:do_connect).and_raise(expected_error)
- expect { knife.connect! }.to raise_error(expected_error)
- end
- end
-
- context "and password auth was not used" do
- before do
- allow(connection).to receive(:password_auth?).and_return false
- allow(connection).to receive(:user).and_return "testuser"
- allow(knife).to receive(:connection_protocol).and_return connection_protocol
- end
-
- context "when using ssh" do
- let(:connection_protocol) { "ssh" }
-
- it "warns, prompts for password, then reconnects with a password-enabled configuration using the new password" do
- expect(knife).to receive(:do_connect).and_raise(expected_error_password_prompt)
- expect(knife.ui).to receive(:warn).with(/Failed to auth.*/)
- expect(knife.ui).to receive(:ask).and_return("newpassword")
- # Ensure that we set echo off to prevent showing password on the screen
- expect(knife).to receive(:do_connect) do |opts|
- expect(opts[:password]).to eq "newpassword"
- end
- knife.connect!
- end
- end
-
- context "when using winrm" do
- let(:connection_protocol) { "winrm" }
-
- it "warns, prompts for password, then reconnects with a password-enabled configuration using the new password for" do
- expect(knife).to receive(:do_connect).and_raise(expected_error_password_prompt_winrm)
- expect(knife.ui).to receive(:warn).with(/Failed to auth.*/)
- expect(knife.ui).to receive(:ask).and_return("newpassword")
- # Ensure that we set echo off to prevent showing password on the screen
- expect(knife).to receive(:do_connect) do |opts|
- expect(opts[:password]).to eq "newpassword"
- end
- knife.connect!
- end
- end
- end
- end
- end
-
- it "verifies that a server to bootstrap was given as a command line arg" do
- knife.name_args = nil
- expect(knife).to receive(:check_license)
- expect { knife.run }.to raise_error(SystemExit)
- expect(stderr.string).to match(/ERROR:.+FQDN or ip/)
- end
-
- describe "#bootstrap_context" do
- context "under Windows" do
- let(:windows_test) { true }
- it "creates a WindowsBootstrapContext" do
- require "chef/knife/core/windows_bootstrap_context"
- expect(knife.bootstrap_context.class).to eq Chef::Knife::Core::WindowsBootstrapContext
- end
- end
-
- context "under linux" do
- let(:linux_test) { true }
- it "creates a BootstrapContext" do
- require "chef/knife/core/bootstrap_context"
- expect(knife.bootstrap_context.class).to eq Chef::Knife::Core::BootstrapContext
- end
- end
- end
-
- describe "#config_value" do
- before do
- knife.config[:test_key_a] = "a from cli"
- knife.config[:test_key_b] = "b from cli"
- Chef::Config[:knife][:test_key_a] = "a from Chef::Config"
- Chef::Config[:knife][:test_key_c] = "c from Chef::Config"
- Chef::Config[:knife][:alt_test_key_c] = "alt c from Chef::Config"
- knife.merge_configs
- Chef::Config[:treat_deprecation_warnings_as_errors] = false
- end
-
- it "returns the Chef::Config value from the cli when the CLI key is set" do
- expect(knife.config_value(:test_key_a, :alt_test_key_c)).to eq "a from cli"
- end
-
- it "returns the Chef::Config value from the alternative key when the CLI key is not set" do
- expect(knife.config_value(:test_key_d, :alt_test_key_c)).to eq "alt c from Chef::Config"
- end
-
- it "returns the default value when the key is not provided by CLI or Chef::Config" do
- expect(knife.config_value(:missing_key, :missing_key, "found")).to eq "found"
- end
- end
-
- describe "#upload_bootstrap" do
- before do
- allow(connection).to receive(:temp_dir).and_return(temp_dir)
- allow(connection).to receive(:normalize_path) { |a| a }
- end
-
- let(:content) { "bootstrap script content" }
- context "under Windows" do
- let(:windows_test) { true }
- let(:temp_dir) { "C:/Temp/bootstrap" }
- it "creates a bat file in the temp dir provided by connection, using given content" do
- expect(connection).to receive(:upload_file_content!).with(content, "C:/Temp/bootstrap/bootstrap.bat")
- expect(knife.upload_bootstrap(content)).to eq "C:/Temp/bootstrap/bootstrap.bat"
- end
- end
-
- context "under Linux" do
- let(:linux_test) { true }
- let(:temp_dir) { "/tmp/bootstrap" }
- it "creates a 'sh file in the temp dir provided by connection, using given content" do
- expect(connection).to receive(:upload_file_content!).with(content, "/tmp/bootstrap/bootstrap.sh")
- expect(knife.upload_bootstrap(content)).to eq "/tmp/bootstrap/bootstrap.sh"
- end
- end
- end
-
- describe "#bootstrap_command" do
- context "under Windows" do
- let(:windows_test) { true }
- it "prefixes the command to run under cmd.exe" do
- expect(knife.bootstrap_command("autoexec.bat")).to eq "cmd.exe /C autoexec.bat"
- end
-
- end
- context "under Linux" do
- let(:linux_test) { true }
- it "prefixes the command to run under sh" do
- expect(knife.bootstrap_command("bootstrap.sh")).to eq "sh bootstrap.sh"
- end
-
- context "with --su-user option" do
- let(:connection_obj) { double("connection", transport_options: {}) }
- before do
- knife.config[:su_user] = "root"
- allow(connection).to receive(:connection).and_return(connection_obj)
- end
- it "prefixes the command to run using su -USER -c" do
- expect(knife.bootstrap_command("bootstrap.sh")).to eq "su - #{knife.config[:su_user]} -c 'sh bootstrap.sh'"
- expect(connection_obj.transport_options.key?(:pty)).to eq true
- end
-
- it "sudo appended if --sudo option enabled" do
- knife.config[:use_sudo] = true
- expect(knife.bootstrap_command("bootstrap.sh")).to eq "sudo su - #{knife.config[:su_user]} -c 'sh bootstrap.sh'"
- expect(connection_obj.transport_options.key?(:pty)).to eq true
- end
- end
- end
- end
-
- describe "#default_bootstrap_template" do
- context "under Windows" do
- let(:windows_test) { true }
- it "is windows-chef-client-msi" do
- expect(knife.default_bootstrap_template).to eq "windows-chef-client-msi"
- end
-
- end
- context "under Linux" do
- let(:linux_test) { true }
- it "is chef-full" do
- expect(knife.default_bootstrap_template).to eq "chef-full"
- end
- end
- end
-
- describe "#do_connect" do
- let(:host_descriptor) { "example.com" }
- let(:connection) { double("TrainConnector") }
- let(:connector_mock) { double("TargetResolver", targets: [ connection ]) }
- before do
- allow(knife).to receive(:host_descriptor).and_return host_descriptor
- end
-
- it "creates a TrainConnector and connects it" do
- expect(Chef::Knife::Bootstrap::TrainConnector).to receive(:new).and_return connection
- expect(connection).to receive(:connect!)
- knife.do_connect({})
- end
-
- context "when sshd configured with requiretty" do
- let(:pty_err_msg) { "Sudo requires a TTY. Please see the README on how to configure sudo to allow for non-interactive usage." }
- let(:expected_error) { Train::UserError.new(pty_err_msg, :sudo_no_tty) }
- before do
- allow(connection).to receive(:connect!).and_raise(expected_error)
- end
- it "retry with pty true request option" do
- expect(Chef::Knife::Bootstrap::TrainConnector).to receive(:new).and_return(connection).exactly(2).times
- expect(knife.ui).to receive(:warn).with("#{pty_err_msg} - trying with pty request")
- expect { knife.do_connect({}) }.to raise_error(expected_error)
- end
- end
-
- context "when a train sudo error is thrown for missing terminal" do
- let(:ui_error_msg) { "Sudo password is required for this operation. Please enter password using -P or --ssh-password option" }
- let(:expected_error) { Train::UserError.new(ui_error_msg, :sudo_missing_terminal) }
- before do
- allow(connection).to receive(:connect!).and_raise(expected_error)
- end
- it "outputs user friendly error message" do
- expect { knife.do_connect({}) }.not_to raise_error
- expect(stderr.string).to include(ui_error_msg)
- end
- end
-
- end
-
- describe "validate_winrm_transport_opts!" do
- before do
- allow(knife).to receive(:connection_protocol).and_return connection_protocol
- end
-
- context "when using ssh" do
- let(:connection_protocol) { "ssh" }
- it "returns true" do
- expect(knife.validate_winrm_transport_opts!).to eq true
- end
- end
- context "when using winrm" do
- let(:connection_protocol) { "winrm" }
- context "with plaintext auth" do
- before do
- knife.config[:winrm_auth_method] = "plaintext"
- end
- context "with ssl" do
- before do
- knife.config[:winrm_ssl] = true
- end
- it "will not error because we won't send anything in plaintext regardless" do
- expect(knife.validate_winrm_transport_opts!).to eq true
- end
- end
- context "without ssl" do
- before do
- knife.config[:winrm_ssl] = false
- end
- context "and no validation key exists" do
- before do
- Chef::Config[:validation_key] = "validation_key.pem"
- allow(File).to receive(:exist?).with(/.*validation_key.pem/).and_return false
- end
-
- it "will error because we will generate and send a client key over the wire in plaintext" do
- expect { knife.validate_winrm_transport_opts! }.to raise_error(SystemExit)
- end
-
- end
- context "and a validation key exists" do
- before do
- Chef::Config[:validation_key] = "validation_key.pem"
- allow(File).to receive(:exist?).with(/.*validation_key.pem/).and_return true
- end
- # TODO - don't we still send validation key?
- it "will not error because we don not send client key over the wire" do
- expect(knife.validate_winrm_transport_opts!).to eq true
- end
- end
- end
- end
-
- context "with other auth" do
- before do
- knife.config[:winrm_auth_method] = "kerberos"
- end
-
- context "and no validation key exists" do
- before do
-
- Chef::Config[:validation_key] = "validation_key.pem"
- allow(File).to receive(:exist?).with(/.*validation_key.pem/).and_return false
- end
-
- it "will not error because we're not using plaintext auth" do
- expect(knife.validate_winrm_transport_opts!).to eq true
- end
- end
- context "and a validation key exists" do
- before do
- Chef::Config[:validation_key] = "validation_key.pem"
- allow(File).to receive(:exist?).with(/.*validation_key.pem/).and_return true
- end
-
- it "will not error because a client key won't be sent over the wire in plaintext when a validation key is present" do
- expect(knife.validate_winrm_transport_opts!).to eq true
- end
- end
-
- end
-
- end
-
- end
-
- describe "#winrm_warn_no_ssl_verification" do
- before do
- allow(knife).to receive(:connection_protocol).and_return connection_protocol
- end
-
- context "when using ssh" do
- let(:connection_protocol) { "ssh" }
- it "does not issue a warning" do
- expect(knife.ui).to_not receive(:warn)
- knife.winrm_warn_no_ssl_verification
- end
- end
- context "when using winrm" do
- let(:connection_protocol) { "winrm" }
- context "winrm_no_verify_cert is set" do
- before do
- knife.config[:winrm_no_verify_cert] = true
- end
-
- context "and ca_trust_file is present" do
- before do
- knife.config[:ca_trust_file] = "file"
- end
-
- it "does not issue a warning" do
- expect(knife.ui).to_not receive(:warn)
- knife.winrm_warn_no_ssl_verification
- end
- end
-
- context "and winrm_ssl_peer_fingerprint is present" do
- before do
- knife.config[:winrm_ssl_peer_fingerprint] = "ABCD"
- end
- it "does not issue a warning" do
- expect(knife.ui).to_not receive(:warn)
- knife.winrm_warn_no_ssl_verification
- end
- end
- context "and neither ca_trust_file nor winrm_ssl_peer_fingerprint is present" do
- it "issues a warning" do
- expect(knife.ui).to receive(:warn)
- knife.winrm_warn_no_ssl_verification
- end
- end
- end
- end
- end
-
- describe "#warn_on_short_session_timeout" do
- let(:session_timeout) { 60 }
-
- before do
- allow(knife).to receive(:session_timeout).and_return(session_timeout)
- end
-
- context "timeout is not set at all" do
- let(:session_timeout) { nil }
- it "does not issue a warning" do
- expect(knife.ui).to_not receive(:warn)
- knife.warn_on_short_session_timeout
- end
- end
-
- context "timeout is more than 15" do
- let(:session_timeout) { 16 }
- it "does not issue a warning" do
- expect(knife.ui).to_not receive(:warn)
- knife.warn_on_short_session_timeout
- end
- end
- context "timeout is 15 or less" do
- let(:session_timeout) { 15 }
- it "issues a warning" do
- expect(knife.ui).to receive(:warn)
- knife.warn_on_short_session_timeout
- end
- end
- end
-end
+#
+# Author:: Ian Meyer (<ianmmeyer@gmail.com>)
+# Copyright:: Copyright 2010-2016, Ian Meyer
+# Copyright:: Copyright (c) Chef Software Inc.
+# 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 "knife_spec_helper"
+
+Chef::Knife::Bootstrap.load_deps
+
+describe Chef::Knife::Bootstrap do
+ let(:bootstrap_template) { nil }
+ let(:stderr) { StringIO.new }
+ let(:bootstrap_cli_options) { [ ] }
+ let(:linux_test) { true }
+ let(:windows_test) { false }
+ let(:linux_test) { false }
+ let(:unix_test) { false }
+ let(:ssh_test) { false }
+
+ let(:connection) do
+ double("TrainConnector",
+ windows?: windows_test,
+ linux?: linux_test,
+ unix?: unix_test)
+ end
+
+ let(:knife) do
+ Chef::Log.logger = Logger.new(StringIO.new)
+ Chef::Config[:knife][:bootstrap_template] = bootstrap_template unless bootstrap_template.nil?
+
+ k = Chef::Knife::Bootstrap.new(bootstrap_cli_options)
+ allow(k.ui).to receive(:stderr).and_return(stderr)
+ allow(k).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(false)
+ allow(k).to receive(:connection).and_return connection
+ k.merge_configs
+ k
+ end
+
+ context "#check_license" do
+ let(:acceptor) { instance_double(LicenseAcceptance::Acceptor) }
+
+ before do
+ expect(LicenseAcceptance::Acceptor).to receive(:new).and_return(acceptor)
+ end
+
+ describe "when a license is not required" do
+ it "does not set the chef_license" do
+ expect(acceptor).to receive(:license_required?).and_return(false)
+ knife.check_license
+ expect(Chef::Config[:chef_license]).to eq(nil)
+ end
+ end
+
+ describe "when a license is required" do
+ it "sets the chef_license" do
+ expect(acceptor).to receive(:license_required?).and_return(true)
+ expect(acceptor).to receive(:id_from_mixlib).and_return("id")
+ expect(acceptor).to receive(:check_and_persist)
+ expect(acceptor).to receive(:acceptance_value).and_return("accept-no-persist")
+ knife.check_license
+ expect(Chef::Config[:chef_license]).to eq("accept-no-persist")
+ end
+ end
+ end
+
+ context "#bootstrap_template" do
+ it "should default to chef-full" do
+ expect(knife.bootstrap_template).to be_a_kind_of(String)
+ expect(File.basename(knife.bootstrap_template)).to eq("chef-full")
+ end
+ end
+
+ context "#render_template - when using the chef-full default template" do
+ let(:rendered_template) do
+ knife.merge_configs
+ knife.render_template
+ end
+
+ it "should render client.rb" do
+ expect(rendered_template).to match("cat > /etc/chef/client.rb <<'EOP'")
+ expect(rendered_template).to match("chef_server_url \"https://localhost:443\"")
+ expect(rendered_template).to match("validation_client_name \"chef-validator\"")
+ expect(rendered_template).to match("log_location STDOUT")
+ end
+
+ it "should render first-boot.json" do
+ expect(rendered_template).to match("cat > /etc/chef/first-boot.json <<'EOP'")
+ expect(rendered_template).to match('{"run_list":\[\]}')
+ end
+
+ context "and encrypted_data_bag_secret was provided" do
+ it "should render encrypted_data_bag_secret file" do
+ expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(true)
+ expect(knife).to receive(:read_secret).and_return("secrets")
+ expect(rendered_template).to match("cat > /etc/chef/encrypted_data_bag_secret <<'EOP'")
+ expect(rendered_template).to match('{"run_list":\[\]}')
+ expect(rendered_template).to match(/secrets/)
+ end
+ end
+ end
+
+ context "with --bootstrap-vault-item" do
+ let(:bootstrap_cli_options) { [ "--bootstrap-vault-item", "vault1:item1", "--bootstrap-vault-item", "vault1:item2", "--bootstrap-vault-item", "vault2:item1" ] }
+ it "sets the knife config cli option correctly" do
+ expect(knife.config[:bootstrap_vault_item]).to eq({ "vault1" => %w{item1 item2}, "vault2" => ["item1"] })
+ end
+ end
+
+ context "with --bootstrap-preinstall-command" do
+ command = "while sudo fuser /var/lib/dpkg/lock >/dev/null 2>&1; do\n echo 'waiting for dpkg lock';\n sleep 1;\n done;"
+ let(:bootstrap_cli_options) { [ "--bootstrap-preinstall-command", command ] }
+ let(:rendered_template) do
+ knife.merge_configs
+ knife.render_template
+ end
+ it "configures the preinstall command in the bootstrap template correctly" do
+ expect(rendered_template).to match(/command/)
+ end
+ end
+
+ context "with --bootstrap-proxy" do
+ let(:bootstrap_cli_options) { [ "--bootstrap-proxy", "1.1.1.1" ] }
+ let(:rendered_template) do
+ knife.merge_configs
+ knife.render_template
+ end
+ it "configures the https_proxy environment variable in the bootstrap template correctly" do
+ expect(rendered_template).to match(/https_proxy="1.1.1.1" export https_proxy/)
+ end
+ end
+
+ context "with --bootstrap-no-proxy" do
+ let(:bootstrap_cli_options) { [ "--bootstrap-no-proxy", "localserver" ] }
+ let(:rendered_template) do
+ knife.merge_configs
+ knife.render_template
+ end
+ it "configures the https_proxy environment variable in the bootstrap template correctly" do
+ expect(rendered_template).to match(/no_proxy="localserver" export no_proxy/)
+ end
+ end
+
+ context "with :bootstrap_template and :template_file cli options" do
+ let(:bootstrap_cli_options) { [ "--bootstrap-template", "my-template", "other-template" ] }
+
+ it "should select bootstrap template" do
+ expect(File.basename(knife.bootstrap_template)).to eq("my-template")
+ end
+ end
+
+ context "when finding templates" do
+ context "when :bootstrap_template config is set to a file" do
+ context "that doesn't exist" do
+ let(:bootstrap_template) { "/opt/blah/not/exists/template.erb" }
+
+ it "raises an error" do
+ expect { knife.find_template }.to raise_error(Errno::ENOENT)
+ end
+ end
+
+ context "that exists" do
+ let(:bootstrap_template) { File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "test.erb")) }
+
+ it "loads the given file as the template" do
+ expect(Chef::Log).to receive(:trace)
+ expect(knife.find_template).to eq(File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "test.erb")))
+ end
+ end
+ end
+
+ context "when :bootstrap_template config is set to a template name" do
+ let(:bootstrap_template) { "example" }
+
+ let(:builtin_template_path) { File.expand_path(File.join(__dir__, "../../../lib/chef/knife/bootstrap/templates", "example.erb")) }
+
+ let(:chef_config_dir_template_path) { "/knife/chef/config/bootstrap/example.erb" }
+
+ let(:env_home_template_path) { "/env/home/.chef/bootstrap/example.erb" }
+
+ let(:gem_files_template_path) { "/Users/schisamo/.rvm/gems/ruby-1.9.2-p180@chef-0.10/gems/knife-windows-0.5.4/lib/chef/knife/bootstrap/fake-bootstrap-template.erb" }
+
+ def configure_chef_config_dir
+ allow(Chef::Knife).to receive(:chef_config_dir).and_return("/knife/chef/config")
+ end
+
+ def configure_env_home
+ allow(Chef::Util::PathHelper).to receive(:home).with(".chef", "bootstrap", "example.erb").and_yield(env_home_template_path)
+ end
+
+ def configure_gem_files
+ allow(Gem).to receive(:find_files).and_return([ gem_files_template_path ])
+ end
+
+ before(:each) do
+ expect(File).to receive(:exist?).with(bootstrap_template).and_return(false)
+ end
+
+ context "when file is available everywhere" do
+ before do
+ configure_chef_config_dir
+ configure_env_home
+ configure_gem_files
+
+ expect(File).to receive(:exist?).with(builtin_template_path).and_return(true)
+ end
+
+ it "should load the template from built-in templates" do
+ expect(knife.find_template).to eq(builtin_template_path)
+ end
+ end
+
+ context "when file is available in chef_config_dir" do
+ before do
+ configure_chef_config_dir
+ configure_env_home
+ configure_gem_files
+
+ expect(File).to receive(:exist?).with(builtin_template_path).and_return(false)
+ expect(File).to receive(:exist?).with(chef_config_dir_template_path).and_return(true)
+
+ it "should load the template from chef_config_dir" do
+ knife.find_template.should eq(chef_config_dir_template_path)
+ end
+ end
+ end
+
+ context "when file is available in home directory" do
+ before do
+ configure_chef_config_dir
+ configure_env_home
+ configure_gem_files
+
+ expect(File).to receive(:exist?).with(builtin_template_path).and_return(false)
+ expect(File).to receive(:exist?).with(chef_config_dir_template_path).and_return(false)
+ expect(File).to receive(:exist?).with(env_home_template_path).and_return(true)
+ end
+
+ it "should load the template from chef_config_dir" do
+ expect(knife.find_template).to eq(env_home_template_path)
+ end
+ end
+
+ context "when file is available in Gem files" do
+ before do
+ configure_chef_config_dir
+ configure_env_home
+ configure_gem_files
+
+ expect(File).to receive(:exist?).with(builtin_template_path).and_return(false)
+ expect(File).to receive(:exist?).with(chef_config_dir_template_path).and_return(false)
+ expect(File).to receive(:exist?).with(env_home_template_path).and_return(false)
+ expect(File).to receive(:exist?).with(gem_files_template_path).and_return(true)
+ end
+
+ it "should load the template from Gem files" do
+ expect(knife.find_template).to eq(gem_files_template_path)
+ end
+ end
+
+ context "when file is available in Gem files and home dir doesn't exist" do
+ before do
+ configure_chef_config_dir
+ configure_gem_files
+ allow(Chef::Util::PathHelper).to receive(:home).with(".chef", "bootstrap", "example.erb").and_return(nil)
+
+ expect(File).to receive(:exist?).with(builtin_template_path).and_return(false)
+ expect(File).to receive(:exist?).with(chef_config_dir_template_path).and_return(false)
+ expect(File).to receive(:exist?).with(gem_files_template_path).and_return(true)
+ end
+
+ it "should load the template from Gem files" do
+ expect(knife.find_template).to eq(gem_files_template_path)
+ end
+ end
+ end
+ end
+
+ ["-t", "--bootstrap-template"].each do |t|
+ context "when #{t} option is given in the command line" do
+ it "sets the knife :bootstrap_template config" do
+ knife.parse_options([t, "blahblah"])
+ knife.merge_configs
+ expect(knife.bootstrap_template).to eq("blahblah")
+ end
+ end
+ end
+
+ context "with run_list template" do
+ let(:bootstrap_template) { File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "test.erb")) }
+
+ it "should return an empty run_list" do
+ expect(knife.render_template).to eq('{"run_list":[]}')
+ end
+
+ it "should have role[base] in the run_list" do
+ knife.parse_options(["-r", "role[base]"])
+ knife.merge_configs
+ expect(knife.render_template).to eq('{"run_list":["role[base]"]}')
+ end
+
+ it "should have role[base] and recipe[cupcakes] in the run_list" do
+ knife.parse_options(["-r", "role[base],recipe[cupcakes]"])
+ knife.merge_configs
+ expect(knife.render_template).to eq('{"run_list":["role[base]","recipe[cupcakes]"]}')
+ end
+
+ context "with bootstrap_attribute options" do
+ let(:jsonfile) do
+ file = Tempfile.new(["node", ".json"])
+ File.open(file.path, "w") { |f| f.puts '{"foo":{"bar":"baz"}}' }
+ file
+ end
+
+ it "should have foo => {bar => baz} in the first_boot from cli" do
+ knife.parse_options(["-j", '{"foo":{"bar":"baz"}}'])
+ knife.merge_configs
+ expected_hash = FFI_Yajl::Parser.new.parse('{"foo":{"bar":"baz"},"run_list":[]}')
+ actual_hash = FFI_Yajl::Parser.new.parse(knife.render_template)
+ expect(actual_hash).to eq(expected_hash)
+ end
+
+ it "should have foo => {bar => baz} in the first_boot from file" do
+ knife.parse_options(["--json-attribute-file", jsonfile.path])
+ knife.merge_configs
+ expected_hash = FFI_Yajl::Parser.new.parse('{"foo":{"bar":"baz"},"run_list":[]}')
+ actual_hash = FFI_Yajl::Parser.new.parse(knife.render_template)
+ expect(actual_hash).to eq(expected_hash)
+ jsonfile.close
+ end
+
+ it "raises a Chef::Exceptions::BootstrapCommandInputError with the proper error message" do
+ knife.parse_options(["-j", '{"foo":{"bar":"baz"}}'])
+ knife.parse_options(["--json-attribute-file", jsonfile.path])
+ knife.merge_configs
+ allow(knife).to receive(:validate_name_args!)
+ expect(knife).to receive(:check_license)
+
+ expect { knife.run }.to raise_error(Chef::Exceptions::BootstrapCommandInputError)
+ jsonfile.close
+ end
+ end
+ end
+
+ context "with hints template" do
+ let(:bootstrap_template) { File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "test-hints.erb")) }
+
+ it "should create a hint file when told to" do
+ knife.parse_options(["--hint", "openstack"])
+ knife.merge_configs
+ expect(knife.render_template).to match(%r{/etc/chef/ohai/hints/openstack.json})
+ end
+
+ it "should populate a hint file with JSON when given a file to read" do
+ allow(::File).to receive(:read).and_return('{ "foo" : "bar" }')
+ knife.parse_options(["--hint", "openstack=hints/openstack.json"])
+ knife.merge_configs
+ expect(knife.render_template).to match(/\{\"foo\":\"bar\"\}/)
+ end
+ end
+
+ describe "specifying no_proxy with various entries" do
+ subject(:knife) do
+ k = described_class.new
+ Chef::Config[:knife][:bootstrap_template] = template_file
+ allow(k).to receive(:connection).and_return connection
+ k.parse_options(options)
+ k.merge_configs
+ k
+ end
+
+ let(:options) { ["--bootstrap-no-proxy", setting] }
+
+ let(:template_file) { File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "no_proxy.erb")) }
+
+ let(:rendered_template) do
+ knife.render_template
+ end
+
+ context "via --bootstrap-no-proxy" do
+ let(:setting) { "api.opscode.com" }
+
+ it "renders the client.rb with a single FQDN no_proxy entry" do
+ expect(rendered_template).to match(/.*no_proxy\s*"api.opscode.com".*/)
+ end
+ end
+
+ context "via --bootstrap-no-proxy multiple" do
+ let(:setting) { "api.opscode.com,172.16.10.*" }
+
+ it "renders the client.rb with comma-separated FQDN and wildcard IP address no_proxy entries" do
+ expect(rendered_template).to match(/.*no_proxy\s*"api.opscode.com,172.16.10.\*".*/)
+ end
+ end
+
+ context "via --ssl-verify-mode none" do
+ let(:options) { ["--node-ssl-verify-mode", "none"] }
+
+ it "renders the client.rb with ssl_verify_mode set to :verify_none" do
+ expect(rendered_template).to match(/ssl_verify_mode :verify_none/)
+ end
+ end
+
+ context "via --node-ssl-verify-mode peer" do
+ let(:options) { ["--node-ssl-verify-mode", "peer"] }
+
+ it "renders the client.rb with ssl_verify_mode set to :verify_peer" do
+ expect(rendered_template).to match(/ssl_verify_mode :verify_peer/)
+ end
+ end
+
+ context "via --node-ssl-verify-mode all" do
+ let(:options) { ["--node-ssl-verify-mode", "all"] }
+
+ it "raises error" do
+ expect { rendered_template }.to raise_error(RuntimeError)
+ end
+ end
+
+ context "via --node-verify-api-cert" do
+ let(:options) { ["--node-verify-api-cert"] }
+
+ it "renders the client.rb with verify_api_cert set to true" do
+ expect(rendered_template).to match(/verify_api_cert true/)
+ end
+ end
+
+ context "via --no-node-verify-api-cert" do
+ let(:options) { ["--no-node-verify-api-cert"] }
+
+ it "renders the client.rb with verify_api_cert set to false" do
+ expect(rendered_template).to match(/verify_api_cert false/)
+ end
+ end
+ end
+
+ describe "specifying the encrypted data bag secret key" do
+ let(:secret) { "supersekret" }
+ let(:options) { [] }
+ let(:bootstrap_template) { File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "secret.erb")) }
+ let(:rendered_template) do
+ knife.parse_options(options)
+ knife.merge_configs
+ knife.render_template
+ end
+
+ it "creates a secret file" do
+ expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(true)
+ expect(knife).to receive(:read_secret).and_return(secret)
+ expect(rendered_template).to match(/#{secret}/)
+ end
+
+ it "renders the client.rb with an encrypted_data_bag_secret entry" do
+ expect(knife).to receive(:encryption_secret_provided_ignore_encrypt_flag?).and_return(true)
+ expect(knife).to receive(:read_secret).and_return(secret)
+ expect(rendered_template).to match(%r{encrypted_data_bag_secret\s*"/etc/chef/encrypted_data_bag_secret"})
+ end
+
+ end
+
+ describe "when transferring trusted certificates" do
+ let(:rendered_template) do
+ knife.merge_configs
+ knife.render_template
+ end
+
+ before do
+ Chef::Config[:trusted_certs_dir] = Chef::Util::PathHelper.cleanpath(File.join(CHEF_SPEC_DATA, "trusted_certs"))
+ end
+
+ it "creates /etc/chef/trusted_certs" do
+ expect(rendered_template).to match(%r{mkdir -p /etc/chef/trusted_certs})
+ end
+
+ it "copies the certificates in the directory" do
+ certificates = Dir[File.join(Chef::Config[:trusted_certs_dir], "*.{crt,pem}")]
+
+ certificates.each do |cert|
+ expect(rendered_template).to match(%r{cat > /etc/chef/trusted_certs/#{File.basename(cert)} <<'EOP'})
+ end
+ end
+
+ it "doesn't create /etc/chef/trusted_certs if :trusted_certs_dir is empty" do
+ Dir.mktmpdir do |dir|
+ Chef::Config[:trusted_certs_dir] = dir
+ expect(rendered_template).not_to match(%r{mkdir -p /etc/chef/trusted_certs})
+ end
+ end
+ end
+
+ context "when doing fips things" do
+ let(:template_file) { File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "no_proxy.erb")) }
+
+ before do
+ Chef::Config[:knife][:bootstrap_template] = template_file
+ knife.merge_configs
+ end
+
+ let(:rendered_template) do
+ knife.render_template
+ end
+
+ context "when knife is in fips mode" do
+ before do
+ Chef::Config[:fips] = true
+ end
+
+ it "renders 'fips true'" do
+ expect(rendered_template).to match("fips")
+ end
+ end
+
+ context "when knife is not in fips mode" do
+ before do
+ # This is required because the chef-fips pipeline does
+ # has a default value of true for fips
+ Chef::Config[:fips] = false
+ end
+
+ it "does not render anything about fips" do
+ expect(rendered_template).not_to match("fips")
+ end
+ end
+ end
+
+ describe "when transferring client.d" do
+
+ let(:rendered_template) do
+ knife.merge_configs
+ knife.render_template
+ end
+
+ before do
+ Chef::Config[:client_d_dir] = client_d_dir
+ end
+
+ context "when client_d_dir is nil" do
+ let(:client_d_dir) { nil }
+
+ it "does not create /etc/chef/client.d" do
+ expect(rendered_template).not_to match(%r{mkdir -p /etc/chef/client\.d})
+ end
+ end
+
+ context "when client_d_dir is set" do
+ let(:client_d_dir) do
+ Chef::Util::PathHelper.cleanpath(
+ File.join(__dir__, "../../data/client.d_00")
+ )
+ end
+
+ it "creates /etc/chef/client.d" do
+ expect(rendered_template).to match("mkdir -p /etc/chef/client\.d")
+ end
+
+ context "a flat directory structure" do
+ it "escapes single-quotes" do
+ expect(rendered_template).to match("cat > /etc/chef/client.d/02-strings.rb <<'EOP'")
+ expect(rendered_template).to match("something '\\\\''/foo/bar'\\\\''")
+ end
+
+ it "creates a file 00-foo.rb" do
+ expect(rendered_template).to match("cat > /etc/chef/client.d/00-foo.rb <<'EOP'")
+ expect(rendered_template).to match("d6f9b976-289c-4149-baf7-81e6ffecf228")
+ end
+ it "creates a file bar" do
+ expect(rendered_template).to match("cat > /etc/chef/client.d/bar <<'EOP'")
+ expect(rendered_template).to match("1 / 0")
+ end
+ end
+
+ context "a nested directory structure" do
+ let(:client_d_dir) do
+ Chef::Util::PathHelper.cleanpath(
+ File.join(__dir__, "../../data/client.d_01")
+ )
+ end
+ it "creates a file foo/bar.rb" do
+ expect(rendered_template).to match("cat > /etc/chef/client.d/foo/bar.rb <<'EOP'")
+ expect(rendered_template).to match("1 / 0")
+ end
+ end
+ end
+ end
+
+ describe "#connection_protocol" do
+ let(:host_descriptor) { "example.com" }
+ let(:config) { {} }
+ let(:knife_connection_protocol) { nil }
+ before do
+ allow(knife).to receive(:config).and_return config
+ allow(knife).to receive(:host_descriptor).and_return host_descriptor
+ if knife_connection_protocol
+ Chef::Config[:knife][:connection_protocol] = knife_connection_protocol
+ knife.merge_configs
+ end
+ end
+
+ context "when protocol is part of the host argument" do
+ let(:host_descriptor) { "winrm://myhost" }
+
+ it "returns the value provided by the host argument" do
+ expect(knife.connection_protocol).to eq "winrm"
+ end
+ end
+
+ context "when protocol is provided via the CLI flag" do
+ let(:config) { { connection_protocol: "winrm" } }
+ it "returns that value" do
+ expect(knife.connection_protocol).to eq "winrm"
+ end
+
+ end
+ context "when protocol is provided via the host argument and the CLI flag" do
+ let(:host_descriptor) { "ssh://example.com" }
+ let(:config) { { connection_protocol: "winrm" } }
+
+ it "returns the value provided by the host argument" do
+ expect(knife.connection_protocol).to eq "ssh"
+ end
+ end
+
+ context "when no explicit protocol is provided" do
+ let(:config) { {} }
+ let(:host_descriptor) { "example.com" }
+ let(:knife_connection_protocol) { "winrm" }
+ it "falls back to knife config" do
+ expect(knife.connection_protocol).to eq "winrm"
+ end
+ context "and there is no knife bootstrap_protocol" do
+ let(:knife_connection_protocol) { nil }
+ it "falls back to 'ssh'" do
+ expect(knife.connection_protocol).to eq "ssh"
+ end
+ end
+ end
+
+ end
+
+ describe "#validate_protocol!" do
+ let(:host_descriptor) { "example.com" }
+ let(:config) { {} }
+ let(:connection_protocol) { "ssh" }
+ before do
+ allow(knife).to receive(:config).and_return config
+ allow(knife).to receive(:connection_protocol).and_return connection_protocol
+ allow(knife).to receive(:host_descriptor).and_return host_descriptor
+ end
+
+ context "when protocol is provided both in the URL and via --protocol" do
+
+ context "and they do not match" do
+ let(:connection_protocol) { "ssh" }
+ let(:config) { { connection_protocol: "winrm" } }
+ it "outputs an error and exits" do
+ expect(knife.ui).to receive(:error)
+ expect { knife.validate_protocol! }.to raise_error SystemExit
+ end
+ end
+
+ context "and they do match" do
+ let(:connection_protocol) { "winrm" }
+ let(:config) { { connection_protocol: "winrm" } }
+ it "returns true" do
+ expect(knife.validate_protocol!).to eq true
+ end
+ end
+ end
+
+ context "and the protocol is supported" do
+
+ Chef::Knife::Bootstrap::SUPPORTED_CONNECTION_PROTOCOLS.each do |proto|
+ let(:connection_protocol) { proto }
+ it "returns true for #{proto}" do
+ expect(knife.validate_protocol!).to eq true
+ end
+ end
+ end
+
+ context "and the protocol is not supported" do
+ let(:connection_protocol) { "invalid" }
+ it "outputs an error and exits" do
+ expect(knife.ui).to receive(:error).with(/Unsupported protocol '#{connection_protocol}'/)
+ expect { knife.validate_protocol! }.to raise_error SystemExit
+ end
+ end
+ end
+
+ describe "#validate_policy_options!" do
+
+ context "when only policy_name is given" do
+
+ let(:bootstrap_cli_options) { %w{ --policy-name my-app-server } }
+
+ it "returns an error stating that policy_name and policy_group must be given together" do
+ expect { knife.validate_policy_options! }.to raise_error(SystemExit)
+ expect(stderr.string).to include("ERROR: --policy-name and --policy-group must be specified together")
+ end
+
+ end
+
+ context "when only policy_group is given" do
+
+ let(:bootstrap_cli_options) { %w{ --policy-group staging } }
+
+ it "returns an error stating that policy_name and policy_group must be given together" do
+ expect { knife.validate_policy_options! }.to raise_error(SystemExit)
+ expect(stderr.string).to include("ERROR: --policy-name and --policy-group must be specified together")
+ end
+
+ end
+
+ context "when both policy_name and policy_group are given, but run list is also given" do
+
+ let(:bootstrap_cli_options) { %w{ --policy-name my-app --policy-group staging --run-list cookbook } }
+
+ it "returns an error stating that policyfile and run_list are exclusive" do
+ expect { knife.validate_policy_options! }.to raise_error(SystemExit)
+ expect(stderr.string).to include("ERROR: Policyfile options and --run-list are exclusive")
+ end
+
+ end
+
+ context "when policy_name and policy_group are given with no conflicting options" do
+
+ let(:bootstrap_cli_options) { %w{ --policy-name my-app --policy-group staging } }
+
+ it "passes options validation" do
+ expect { knife.validate_policy_options! }.to_not raise_error
+ end
+
+ it "passes them into the bootstrap context" do
+ expect(knife.bootstrap_context.first_boot).to have_key(:policy_name)
+ expect(knife.bootstrap_context.first_boot).to have_key(:policy_group)
+ end
+
+ it "ensures that run_list is not set in the bootstrap context" do
+ expect(knife.bootstrap_context.first_boot).to_not have_key(:run_list)
+ end
+
+ end
+
+ # https://github.com/chef/chef/issues/4131
+ # Arguably a bug in the plugin: it shouldn't be setting this to nil, but it
+ # worked before, so make it work now.
+ context "when a plugin sets the run list option to nil" do
+ before do
+ knife.config[:run_list] = nil
+ end
+
+ it "passes options validation" do
+ expect { knife.validate_policy_options! }.to_not raise_error
+ end
+ end
+ end
+
+ # TODO - this is the only cli option we validate the _option_ itself -
+ # so we'll know if someone accidentally deletes or renames use_sudo_password
+ # Is this worht keeping? If so, then it seems we should expand it
+ # to cover all options.
+ context "validating use_sudo_password option" do
+ it "use_sudo_password contains description and long params for help" do
+ expect(knife.options).to(have_key(:use_sudo_password)) \
+ && expect(knife.options[:use_sudo_password][:description].to_s).not_to(eq(""))\
+ && expect(knife.options[:use_sudo_password][:long].to_s).not_to(eq(""))
+ end
+ end
+
+ context "#connection_opts" do
+ let(:connection_protocol) { "ssh" }
+ before do
+ allow(knife).to receive(:connection_protocol).and_return connection_protocol
+ end
+ context "behavioral test: " do
+ let(:expected_connection_opts) do
+ { base_opts: true,
+ ssh_identity_opts: true,
+ ssh_opts: true,
+ gateway_opts: true,
+ host_verify_opts: true,
+ sudo_opts: true,
+ winrm_opts: true }
+ end
+
+ it "queries and merges only expected configurations" do
+ expect(knife).to receive(:base_opts).and_return({ base_opts: true })
+ expect(knife).to receive(:host_verify_opts).and_return({ host_verify_opts: true })
+ expect(knife).to receive(:gateway_opts).and_return({ gateway_opts: true })
+ expect(knife).to receive(:sudo_opts).and_return({ sudo_opts: true })
+ expect(knife).to receive(:winrm_opts).and_return({ winrm_opts: true })
+ expect(knife).to receive(:ssh_opts).and_return({ ssh_opts: true })
+ expect(knife).to receive(:ssh_identity_opts).and_return({ ssh_identity_opts: true })
+ expect(knife.connection_opts).to match expected_connection_opts
+ end
+ end
+
+ context "functional test: " do
+ context "when protocol is winrm" do
+ let(:connection_protocol) { "winrm" }
+ # context "and neither CLI nor Chef::Config config entries have been provided"
+ # end
+ context "and all supported values are provided as Chef::Config entries" do
+ before do
+ # Set everything to easily identifiable and obviously fake values
+ # to verify that Chef::Config is being sourced instead of knife.config
+ knife.config = {}
+ Chef::Config[:knife][:max_wait] = 9999.0
+ Chef::Config[:knife][:winrm_user] = "winbob"
+ Chef::Config[:knife][:winrm_port] = 9999
+ Chef::Config[:knife][:ca_trust_file] = "trust.me"
+ Chef::Config[:knife][:kerberos_realm] = "realm"
+ Chef::Config[:knife][:kerberos_service] = "service"
+ Chef::Config[:knife][:winrm_auth_method] = "kerberos" # default is negotiate
+ Chef::Config[:knife][:winrm_basic_auth_only] = true
+ Chef::Config[:knife][:winrm_no_verify_cert] = true
+ Chef::Config[:knife][:session_timeout] = 9999
+ Chef::Config[:knife][:winrm_ssl] = true
+ Chef::Config[:knife][:winrm_ssl_peer_fingerprint] = "ABCDEF"
+ end
+
+ context "and no CLI options have been given" do
+ let(:expected_result) do
+ {
+ logger: Chef::Log, # not configurable
+ ca_trust_path: "trust.me",
+ max_wait_until_ready: 9999, # converted to int
+ operation_timeout: 9999,
+ ssl_peer_fingerprint: "ABCDEF",
+ winrm_transport: "kerberos",
+ winrm_basic_auth_only: true,
+ user: "winbob",
+ port: 9999,
+ self_signed: true,
+ ssl: true,
+ kerberos_realm: "realm",
+ kerberos_service: "service",
+ }
+ end
+
+ it "generates a config hash using the Chef::Config values" do
+ knife.merge_configs
+ expect(knife.connection_opts).to match expected_result
+ end
+
+ end
+
+ context "and some CLI options have been given" do
+ let(:expected_result) do
+ {
+ logger: Chef::Log, # not configurable
+ ca_trust_path: "no trust",
+ max_wait_until_ready: 9999,
+ operation_timeout: 9999,
+ ssl_peer_fingerprint: "ABCDEF",
+ winrm_transport: "kerberos",
+ winrm_basic_auth_only: true,
+ user: "microsoftbob",
+ port: 12,
+ self_signed: true,
+ ssl: true,
+ kerberos_realm: "realm",
+ kerberos_service: "service",
+ password: "lobster",
+ }
+ end
+
+ before do
+ knife.config[:ca_trust_file] = "no trust"
+ knife.config[:connection_user] = "microsoftbob"
+ knife.config[:connection_port] = 12
+ knife.config[:winrm_port] = "13" # indirectly verify we're not looking for the wrong CLI flag
+ knife.config[:connection_password] = "lobster"
+ end
+
+ it "generates a config hash using the CLI options when available and falling back to Chef::Config values" do
+ knife.merge_configs
+ expect(knife.connection_opts).to match expected_result
+ end
+ end
+
+ context "and all CLI options have been given" do
+ before do
+ # We'll force kerberos vi knife.config because it
+ # causes additional options to populate - make sure
+ # Chef::Config is different so we can be sure that we didn't
+ # pull in the Chef::Config value
+ Chef::Config[:knife][:winrm_auth_method] = "negotiate"
+ knife.config[:connection_password] = "blue"
+ knife.config[:max_wait] = 1000.0
+ knife.config[:connection_user] = "clippy"
+ knife.config[:connection_port] = 1000
+ knife.config[:winrm_port] = 1001 # We should not see this value get used
+
+ knife.config[:ca_trust_file] = "trust.the.internet"
+ knife.config[:kerberos_realm] = "otherrealm"
+ knife.config[:kerberos_service] = "otherservice"
+ knife.config[:winrm_auth_method] = "kerberos" # default is negotiate
+ knife.config[:winrm_basic_auth_only] = false
+ knife.config[:winrm_no_verify_cert] = false
+ knife.config[:session_timeout] = 1000
+ knife.config[:winrm_ssl] = false
+ knife.config[:winrm_ssl_peer_fingerprint] = "FEDCBA"
+ end
+ let(:expected_result) do
+ {
+ logger: Chef::Log, # not configurable
+ ca_trust_path: "trust.the.internet",
+ max_wait_until_ready: 1000, # converted to int
+ operation_timeout: 1000,
+ ssl_peer_fingerprint: "FEDCBA",
+ winrm_transport: "kerberos",
+ winrm_basic_auth_only: false,
+ user: "clippy",
+ port: 1000,
+ self_signed: false,
+ ssl: false,
+ kerberos_realm: "otherrealm",
+ kerberos_service: "otherservice",
+ password: "blue",
+ }
+ end
+ it "generates a config hash using the CLI options and pulling nothing from Chef::Config" do
+ knife.merge_configs
+ expect(knife.connection_opts).to match expected_result
+ end
+ end
+ end # with underlying Chef::Config values
+
+ context "and no values are provided from Chef::Config or CLI" do
+ before do
+ # We will use knife's actual config since these tests
+ # have assumptions based on CLI default values
+ end
+ let(:expected_result) do
+ {
+ logger: Chef::Log,
+ operation_timeout: 60,
+ self_signed: false,
+ ssl: false,
+ ssl_peer_fingerprint: nil,
+ winrm_basic_auth_only: false,
+ winrm_transport: "negotiate",
+ }
+ end
+ it "populates appropriate defaults" do
+ knife.merge_configs
+ expect(knife.connection_opts).to match expected_result
+ end
+ end
+ end # winrm
+
+ context "when protocol is ssh" do
+ let(:connection_protocol) { "ssh" }
+ # context "and neither CLI nor Chef::Config config entries have been provided"
+ # end
+ context "and all supported values are provided as Chef::Config entries" do
+ before do
+ # Set everything to easily identifiable and obviously fake values
+ # to verify that Chef::Config is being sourced instead of knife.config
+ knife.config = {}
+ Chef::Config[:knife][:max_wait] = 9999.0
+ Chef::Config[:knife][:session_timeout] = 9999
+ Chef::Config[:knife][:ssh_user] = "sshbob"
+ Chef::Config[:knife][:ssh_port] = 9999
+ Chef::Config[:knife][:host_key_verify] = false
+ Chef::Config[:knife][:ssh_gateway_identity] = "/gateway.pem"
+ Chef::Config[:knife][:ssh_gateway] = "admin@mygateway.local:1234"
+ Chef::Config[:knife][:ssh_identity_file] = "/identity.pem"
+ Chef::Config[:knife][:use_sudo_password] = false # We have no password.
+ end
+
+ context "and no CLI options have been given" do
+ let(:expected_result) do
+ {
+ logger: Chef::Log, # not configurable
+ max_wait_until_ready: 9999, # converted to int
+ connection_timeout: 9999,
+ user: "sshbob",
+ bastion_host: "mygateway.local",
+ bastion_port: 1234,
+ bastion_user: "admin",
+ forward_agent: false,
+ keys_only: true,
+ key_files: ["/identity.pem", "/gateway.pem"],
+ sudo: false,
+ verify_host_key: "always",
+ port: 9999,
+ non_interactive: true,
+ }
+ end
+
+ it "generates a correct config hash using the Chef::Config values" do
+ knife.merge_configs
+ expect(knife.connection_opts).to match expected_result
+ end
+ end
+
+ context "and unsupported Chef::Config options are given in Chef::Config, not in CLI" do
+ before do
+ Chef::Config[:knife][:password] = "blah"
+ Chef::Config[:knife][:ssh_password] = "blah"
+ Chef::Config[:knife][:preserve_home] = true
+ Chef::Config[:knife][:use_sudo] = true
+ Chef::Config[:knife][:ssh_forward_agent] = "blah"
+ end
+ it "does not include the corresponding option in the connection options" do
+ knife.merge_configs
+ expect(knife.connection_opts.key?(:password)).to eq false
+ expect(knife.connection_opts.key?(:ssh_forward_agent)).to eq false
+ expect(knife.connection_opts.key?(:use_sudo)).to eq false
+ expect(knife.connection_opts.key?(:preserve_home)).to eq false
+ end
+ end
+
+ context "and some CLI options have been given" do
+ before do
+ knife.config = {}
+ knife.config[:connection_user] = "sshalice"
+ knife.config[:connection_port] = 12
+ knife.config[:ssh_port] = "13" # canary to indirectly verify we're not looking for the wrong CLI flag
+ knife.config[:connection_password] = "feta cheese"
+ knife.config[:max_wait] = 150.0
+ knife.config[:session_timeout] = 120
+ knife.config[:use_sudo] = true
+ knife.config[:use_sudo_pasword] = true
+ knife.config[:ssh_forward_agent] = true
+ end
+
+ let(:expected_result) do
+ {
+ logger: Chef::Log, # not configurable
+ max_wait_until_ready: 150, # cli (converted to int)
+ connection_timeout: 120, # cli
+ user: "sshalice", # cli
+ password: "feta cheese", # cli
+ bastion_host: "mygateway.local", # Config
+ bastion_port: 1234, # Config
+ bastion_user: "admin", # Config
+ forward_agent: true, # cli
+ keys_only: false, # implied false from config password present
+ key_files: ["/identity.pem", "/gateway.pem"], # Config
+ sudo: true, # ccli
+ verify_host_key: "always", # Config
+ port: 12, # cli
+ non_interactive: true,
+ }
+ end
+
+ it "generates a config hash using the CLI options when available and falling back to Chef::Config values" do
+ knife.merge_configs
+ expect(knife.connection_opts).to match expected_result
+ end
+ end
+
+ context "and all CLI options have been given" do
+ before do
+ knife.config = {}
+ knife.config[:max_wait] = 150.0
+ knife.config[:session_timeout] = 120
+ knife.config[:connection_user] = "sshroot"
+ knife.config[:connection_port] = 1000
+ knife.config[:connection_password] = "blah"
+ knife.config[:forward_agent] = true
+ knife.config[:use_sudo] = true
+ knife.config[:use_sudo_password] = true
+ knife.config[:preserve_home] = true
+ knife.config[:use_sudo_pasword] = true
+ knife.config[:ssh_forward_agent] = true
+ knife.config[:ssh_verify_host_key] = true
+ knife.config[:ssh_gateway_identity] = "/gateway-identity.pem"
+ knife.config[:ssh_gateway] = "me@example.com:10"
+ knife.config[:ssh_identity_file] = "/my-identity.pem"
+
+ # We'll set these as canaries - if one of these values shows up
+ # in a failed test, then the behavior of not pulling from these keys
+ # out of knife.config is broken:
+ knife.config[:ssh_user] = "do not use"
+ knife.config[:ssh_port] = 1001
+ end
+ let(:expected_result) do
+ {
+ logger: Chef::Log, # not configurable
+ max_wait_until_ready: 150, # converted to int
+ connection_timeout: 120,
+ user: "sshroot",
+ password: "blah",
+ port: 1000,
+ bastion_host: "example.com",
+ bastion_port: 10,
+ bastion_user: "me",
+ forward_agent: true,
+ keys_only: false,
+ key_files: ["/my-identity.pem", "/gateway-identity.pem"],
+ sudo: true,
+ sudo_options: "-H",
+ sudo_password: "blah",
+ verify_host_key: true,
+ non_interactive: true,
+ }
+ end
+ it "generates a config hash using the CLI options and pulling nothing from Chef::Config" do
+ knife.merge_configs
+ expect(knife.connection_opts).to match expected_result
+ end
+ end
+ end
+ context "and no values are provided from Chef::Config or CLI" do
+ before do
+ # We will use knife's actual config since these tests
+ # have assumptions based on CLI default values
+ config = {}
+ end
+
+ let(:expected_result) do
+ {
+ forward_agent: false,
+ key_files: [],
+ logger: Chef::Log,
+ keys_only: false,
+ sudo: false,
+ verify_host_key: "always",
+ non_interactive: true,
+ connection_timeout: 60,
+ }
+ end
+ it "populates appropriate defaults" do
+ knife.merge_configs
+ expect(knife.connection_opts).to match expected_result
+ end
+ end
+
+ end # ssh
+ end # functional tests
+
+ end # connection_opts
+
+ context "#base_opts" do
+ let(:connection_protocol) { nil }
+
+ before do
+ allow(knife).to receive(:connection_protocol).and_return connection_protocol
+ end
+
+ context "for all protocols" do
+ context "when password is provided" do
+ before do
+ knife.config[:connection_port] = 250
+ knife.config[:connection_user] = "test"
+ knife.config[:connection_password] = "opscode"
+ end
+
+ let(:expected_opts) do
+ {
+ port: 250,
+ user: "test",
+ logger: Chef::Log,
+ password: "opscode",
+ }
+ end
+ it "generates the correct options" do
+ expect(knife.base_opts).to eq expected_opts
+ end
+
+ end
+
+ context "when password is not provided" do
+ before do
+ knife.config[:connection_port] = 250
+ knife.config[:connection_user] = "test"
+ end
+
+ let(:expected_opts) do
+ {
+ port: 250,
+ user: "test",
+ logger: Chef::Log,
+ }
+ end
+ it "generates the correct options" do
+ expect(knife.base_opts).to eq expected_opts
+ end
+ end
+ end
+ end
+
+ context "#host_verify_opts" do
+ let(:connection_protocol) { nil }
+ before do
+ allow(knife).to receive(:connection_protocol).and_return connection_protocol
+ end
+
+ context "for winrm" do
+ let(:connection_protocol) { "winrm" }
+ it "returns the expected configuration" do
+ knife.config[:winrm_no_verify_cert] = true
+ expect(knife.host_verify_opts).to eq( { self_signed: true } )
+ end
+ it "provides a correct default when no option given" do
+ expect(knife.host_verify_opts).to eq( { self_signed: false } )
+ end
+ end
+
+ context "for ssh" do
+ let(:connection_protocol) { "ssh" }
+ it "returns the expected configuration" do
+ knife.config[:ssh_verify_host_key] = false
+ expect(knife.host_verify_opts).to eq( { verify_host_key: false } )
+ end
+ it "provides a correct default when no option given" do
+ expect(knife.host_verify_opts).to eq( { verify_host_key: "always" } )
+ end
+ end
+ end
+
+ # TODO - test keys_only, password, config source behavior
+ context "#ssh_identity_opts" do
+ let(:connection_protocol) { nil }
+ before do
+ allow(knife).to receive(:connection_protocol).and_return connection_protocol
+ end
+
+ context "for winrm" do
+ let(:connection_protocol) { "winrm" }
+ it "returns an empty hash" do
+ expect(knife.ssh_identity_opts).to eq({})
+ end
+ end
+
+ context "for ssh" do
+ let(:connection_protocol) { "ssh" }
+ context "when an identity file is specified" do
+ before do
+ knife.config[:ssh_identity_file] = "/identity.pem"
+ end
+ it "generates the expected configuration" do
+ expect(knife.ssh_identity_opts).to eq({
+ key_files: [ "/identity.pem" ],
+ keys_only: true,
+ })
+ end
+ context "and a password is also specified" do
+ before do
+ knife.config[:connection_password] = "blah"
+ end
+ it "generates the expected configuration (key, keys_only false)" do
+ expect(knife.ssh_identity_opts).to eq({
+ key_files: [ "/identity.pem" ],
+ keys_only: false,
+ })
+ end
+ end
+
+ context "and a gateway is not specified" do
+ context "but a gateway identity file is specified" do
+ it "does not include the gateway identity file in keys" do
+ expect(knife.ssh_identity_opts).to eq({
+ key_files: ["/identity.pem"],
+ keys_only: true,
+ })
+ end
+
+ end
+
+ end
+
+ context "and a gatway is specified" do
+ before do
+ knife.config[:ssh_gateway] = "example.com"
+ end
+ context "and a gateway identity file is not specified" do
+ it "config includes only identity file and not gateway identity" do
+ expect(knife.ssh_identity_opts).to eq({
+ key_files: [ "/identity.pem" ],
+ keys_only: true,
+ })
+ end
+ end
+
+ context "and a gateway identity file is also specified" do
+ before do
+ knife.config[:ssh_gateway_identity] = "/gateway.pem"
+ end
+
+ it "generates the expected configuration (both keys, keys_only true)" do
+ expect(knife.ssh_identity_opts).to eq({
+ key_files: [ "/identity.pem", "/gateway.pem" ],
+ keys_only: true,
+ })
+ end
+ end
+ end
+ end
+
+ context "when no identity file is specified" do
+ it "generates the expected configuration (no keys, keys_only false)" do
+ expect(knife.ssh_identity_opts).to eq( {
+ key_files: [],
+ keys_only: false,
+ })
+ end
+ context "and a gateway with gateway identity file is specified" do
+ before do
+ knife.config[:ssh_gateway] = "host"
+ knife.config[:ssh_gateway_identity] = "/gateway.pem"
+ end
+
+ it "generates the expected configuration (gateway key, keys_only false)" do
+ expect(knife.ssh_identity_opts).to eq({
+ key_files: [ "/gateway.pem" ],
+ keys_only: false,
+ })
+ end
+ end
+ end
+ end
+ end
+
+ context "#gateway_opts" do
+ let(:connection_protocol) { nil }
+ before do
+ allow(knife).to receive(:connection_protocol).and_return connection_protocol
+ end
+
+ context "for winrm" do
+ let(:connection_protocol) { "winrm" }
+ it "returns an empty hash" do
+ expect(knife.gateway_opts).to eq({})
+ end
+ end
+
+ context "for ssh" do
+ let(:connection_protocol) { "ssh" }
+ context "and ssh_gateway with hostname, user and port provided" do
+ before do
+ knife.config[:ssh_gateway] = "testuser@gateway:9021"
+ end
+ it "returns a proper bastion host config subset" do
+ expect(knife.gateway_opts).to eq({
+ bastion_user: "testuser",
+ bastion_host: "gateway",
+ bastion_port: 9021,
+ })
+ end
+ end
+ context "and ssh_gateway with only hostname is given" do
+ before do
+ knife.config[:ssh_gateway] = "gateway"
+ end
+ it "returns a proper bastion host config subset" do
+ expect(knife.gateway_opts).to eq({
+ bastion_user: nil,
+ bastion_host: "gateway",
+ bastion_port: nil,
+ })
+ end
+ end
+ context "and ssh_gateway with hostname and user is is given" do
+ before do
+ knife.config[:ssh_gateway] = "testuser@gateway"
+ end
+ it "returns a proper bastion host config subset" do
+ expect(knife.gateway_opts).to eq({
+ bastion_user: "testuser",
+ bastion_host: "gateway",
+ bastion_port: nil,
+ })
+ end
+ end
+
+ context "and ssh_gateway with hostname and port is is given" do
+ before do
+ knife.config[:ssh_gateway] = "gateway:11234"
+ end
+ it "returns a proper bastion host config subset" do
+ expect(knife.gateway_opts).to eq({
+ bastion_user: nil,
+ bastion_host: "gateway",
+ bastion_port: 11234,
+ })
+ end
+ end
+
+ context "and ssh_gateway is not provided" do
+ it "returns an empty hash" do
+ expect(knife.gateway_opts).to eq({})
+ end
+ end
+ end
+ end
+
+ context "#sudo_opts" do
+ let(:connection_protocol) { nil }
+ let(:sudo_pass) { nil }
+ before do
+ allow(knife).to receive(:connection_protocol).and_return connection_protocol
+ end
+
+ context "for winrm" do
+ let(:connection_protocol) { "winrm" }
+ it "returns an empty hash" do
+ expect(knife.sudo_opts(sudo_pass)).to eq({})
+ end
+ end
+
+ context "for ssh" do
+ let(:connection_protocol) { "ssh" }
+ context "when use_sudo is set" do
+ before do
+ knife.config[:use_sudo] = true
+ end
+
+ it "returns a config that enables sudo" do
+ expect(knife.sudo_opts(sudo_pass)).to eq( { sudo: true } )
+ end
+
+ context "when use_sudo_password is also set" do
+ before do
+ knife.config[:use_sudo_password] = true
+ knife.config[:connection_password] = "opscode"
+ end
+ it "includes :connection_password value in a sudo-enabled configuration" do
+ expect(knife.sudo_opts(sudo_pass)).to eq({
+ sudo: true,
+ sudo_password: "opscode",
+ })
+ end
+
+ context "when sudo_pass is set, connection_password is not set" do
+ before do
+ knife.config[:connection_password] = nil
+ end
+ let(:sudo_pass) { "progress" }
+ it "includes :connection_password value in a sudo-enabled configuration" do
+ expect(knife.sudo_opts(sudo_pass)).to eq({
+ sudo: true,
+ sudo_password: "progress",
+ })
+ end
+ end
+
+ context "when connection_password is set, sudo_pass is not set" do
+ before do
+ knife.config[:connection_password] = "opscode"
+ end
+ let(:sudo_pass) { nil }
+ it "includes :connection_password value in a sudo-enabled configuration" do
+ expect(knife.sudo_opts(sudo_pass)).to eq({
+ sudo: true,
+ sudo_password: "opscode",
+ })
+ end
+ end
+
+ context "when connection_password is not set, sudo_pass is not set" do
+ before do
+ knife.config[:connection_password] = nil
+ end
+ let(:sudo_pass) { nil }
+ it "includes :connection_password value in a sudo-enabled configuration" do
+ expect(knife.sudo_opts(sudo_pass)).to eq({
+ sudo: true,
+ sudo_password: nil,
+ })
+ end
+ end
+
+ # connection_password will take precedence here
+ context "when connection_password is set, sudo_pass is set" do
+ before do
+ knife.config[:connection_password] = "opscode"
+ end
+ let(:sudo_pass) { "progress" }
+ it "includes :connection_password value in a sudo-enabled configuration" do
+ expect(knife.sudo_opts(sudo_pass)).to eq({
+ sudo: true,
+ sudo_password: "opscode",
+ })
+ end
+ end
+
+ end
+
+ context "when preserve_home is set" do
+ before do
+ knife.config[:preserve_home] = true
+ end
+ it "enables sudo with sudo_option to preserve home" do
+ expect(knife.sudo_opts(sudo_pass)).to eq({
+ sudo_options: "-H",
+ sudo: true,
+ })
+ end
+ end
+ end
+
+ context "when use_sudo is not set" do
+ before do
+ knife.config[:use_sudo_password] = true
+ knife.config[:preserve_home] = true
+ end
+ it "returns configuration for sudo off, ignoring other related options" do
+ expect(knife.sudo_opts(sudo_pass)).to eq( { sudo: false } )
+ end
+ end
+ end
+ end
+
+ context "#ssh_opts" do
+ let(:connection_protocol) { nil }
+ before do
+ allow(knife).to receive(:connection_protocol).and_return connection_protocol
+ end
+
+ context "for ssh" do
+ let(:connection_protocol) { "ssh" }
+ let(:default_opts) do
+ {
+ non_interactive: true,
+ forward_agent: false,
+ connection_timeout: 60,
+ }
+ end
+
+ context "by default" do
+ it "returns a configuration hash with appropriate defaults" do
+ expect(knife.ssh_opts).to eq default_opts
+ end
+ end
+
+ context "when ssh_forward_agent has a value" do
+ before do
+ knife.config[:ssh_forward_agent] = true
+ end
+ it "returns a default configuration hash with forward_agent set to true" do
+ expect(knife.ssh_opts).to eq(default_opts.merge(forward_agent: true))
+ end
+ end
+ context "when session_timeout has a value" do
+ before do
+ knife.config[:session_timeout] = 120
+ end
+ it "returns a default configuration hash with updated timeout value." do
+ expect(knife.ssh_opts).to eq(default_opts.merge(connection_timeout: 120))
+ end
+ end
+
+ end
+
+ context "for winrm" do
+ let(:connection_protocol) { "winrm" }
+ it "returns an empty has because ssh is not winrm" do
+ expect(knife.ssh_opts).to eq({})
+ end
+ end
+
+ end
+
+ context "#winrm_opts" do
+ let(:connection_protocol) { nil }
+ before do
+ allow(knife).to receive(:connection_protocol).and_return connection_protocol
+ end
+
+ context "for winrm" do
+ let(:connection_protocol) { "winrm" }
+ let(:expected) do
+ {
+ winrm_transport: "negotiate",
+ winrm_basic_auth_only: false,
+ ssl: false,
+ ssl_peer_fingerprint: nil,
+ operation_timeout: 60,
+ }
+ end
+
+ it "generates a correct configuration hash with expected defaults" do
+ expect(knife.winrm_opts).to eq expected
+ end
+
+ context "with ssl_peer_fingerprint" do
+ let(:ssl_peer_fingerprint_expected) do
+ expected.merge({ ssl_peer_fingerprint: "ABCD" })
+ end
+
+ before do
+ knife.config[:winrm_ssl_peer_fingerprint] = "ABCD"
+ end
+
+ it "generates a correct options hash with ssl_peer_fingerprint from the config provided" do
+ expect(knife.winrm_opts).to eq ssl_peer_fingerprint_expected
+ end
+ end
+
+ context "with winrm_ssl" do
+ let(:ssl_expected) do
+ expected.merge({ ssl: true })
+ end
+ before do
+ knife.config[:winrm_ssl] = true
+ end
+
+ it "generates a correct options hash with ssl from the config provided" do
+ expect(knife.winrm_opts).to eq ssl_expected
+ end
+ end
+
+ context "with winrm_auth_method" do
+ let(:winrm_auth_method_expected) do
+ expected.merge({ winrm_transport: "freeaccess" })
+ end
+
+ before do
+ knife.config[:winrm_auth_method] = "freeaccess"
+ end
+
+ it "generates a correct options hash with winrm_transport from the config provided" do
+ expect(knife.winrm_opts).to eq winrm_auth_method_expected
+ end
+ end
+
+ context "with ca_trust_file" do
+ let(:ca_trust_expected) do
+ expected.merge({ ca_trust_path: "/trust.me" })
+ end
+ before do
+ knife.config[:ca_trust_file] = "/trust.me"
+ end
+
+ it "generates a correct options hash with ca_trust_file from the config provided" do
+ expect(knife.winrm_opts).to eq ca_trust_expected
+ end
+ end
+
+ context "with kerberos auth" do
+ let(:kerberos_expected) do
+ expected.merge({
+ kerberos_service: "testsvc",
+ kerberos_realm: "TESTREALM",
+ winrm_transport: "kerberos",
+ })
+ end
+
+ before do
+ knife.config[:winrm_auth_method] = "kerberos"
+ knife.config[:kerberos_service] = "testsvc"
+ knife.config[:kerberos_realm] = "TESTREALM"
+ end
+
+ it "generates a correct options hash containing kerberos auth configuration from the config provided" do
+ expect(knife.winrm_opts).to eq kerberos_expected
+ end
+ end
+
+ context "with winrm_basic_auth_only" do
+ before do
+ knife.config[:winrm_basic_auth_only] = true
+ end
+ let(:basic_auth_expected) do
+ expected.merge( { winrm_basic_auth_only: true } )
+ end
+ it "generates a correct options hash containing winrm_basic_auth_only from the config provided" do
+ expect(knife.winrm_opts).to eq basic_auth_expected
+ end
+ end
+ end
+
+ context "for ssh" do
+ let(:connection_protocol) { "ssh" }
+ it "returns an empty hash because ssh is not winrm" do
+ expect(knife.winrm_opts).to eq({})
+ end
+ end
+ end
+ describe "#run" do
+ it "performs the steps we expect to run a bootstrap" do
+ expect(knife).to receive(:check_license)
+ expect(knife).to receive(:validate_name_args!).ordered
+ expect(knife).to receive(:validate_protocol!).ordered
+ expect(knife).to receive(:validate_first_boot_attributes!).ordered
+ expect(knife).to receive(:validate_winrm_transport_opts!).ordered
+ expect(knife).to receive(:validate_policy_options!).ordered
+ expect(knife).to receive(:winrm_warn_no_ssl_verification).ordered
+ expect(knife).to receive(:warn_on_short_session_timeout).ordered
+ expect(knife).to receive(:connect!).ordered
+ expect(knife).to receive(:register_client).ordered
+ expect(knife).to receive(:render_template).and_return "content"
+ expect(knife).to receive(:upload_bootstrap).with("content").and_return "/remote/path.sh"
+ expect(knife).to receive(:perform_bootstrap).with("/remote/path.sh")
+ expect(connection).to receive(:del_file!) # Make sure cleanup happens
+
+ knife.run
+
+ # Post-run verify expected state changes (not many directly in #run)
+ expect($stdout.sync).to eq true
+ end
+ end
+
+ describe "#register_client" do
+ let(:vault_handler_mock) { double("ChefVaultHandler") }
+ let(:client_builder_mock) { double("ClientBuilder") }
+ let(:node_name) { nil }
+ before do
+ allow(knife).to receive(:chef_vault_handler).and_return vault_handler_mock
+ allow(knife).to receive(:client_builder).and_return client_builder_mock
+ knife.config[:chef_node_name] = node_name
+ end
+
+ shared_examples_for "creating the client locally" do
+ context "when a valid node name is present" do
+ let(:node_name) { "test" }
+ before do
+ allow(client_builder_mock).to receive(:client).and_return "client"
+ allow(client_builder_mock).to receive(:client_path).and_return "/key.pem"
+ end
+
+ it "runs client_builder and vault_handler" do
+ expect(client_builder_mock).to receive(:run)
+ expect(vault_handler_mock).to receive(:run).with("client")
+ knife.register_client
+ end
+
+ it "sets the path to the client key in the bootstrap context" do
+ allow(client_builder_mock).to receive(:run)
+ allow(vault_handler_mock).to receive(:run).with("client")
+ knife.register_client
+ expect(knife.bootstrap_context.client_pem).to eq "/key.pem"
+ end
+ end
+
+ context "when no valid node name is present" do
+ let(:node_name) { nil }
+ it "shows an error and exits" do
+ expect(knife.ui).to receive(:error)
+ expect { knife.register_client }.to raise_error(SystemExit)
+ end
+ end
+ end
+ context "when chef_vault_handler says we're using vault" do
+ let(:vault_handler_mock) { double("ChefVaultHandler") }
+ before do
+ allow(vault_handler_mock).to receive(:doing_chef_vault?).and_return true
+ end
+ it_behaves_like "creating the client locally"
+ end
+
+ context "when an non-existant validation key is specified in chef config" do
+ before do
+ Chef::Config[:validation_key] = "/blah"
+ allow(vault_handler_mock).to receive(:doing_chef_vault?).and_return false
+ allow(File).to receive(:exist?).with(%r{/blah}).and_return false
+ end
+ it_behaves_like "creating the client locally"
+ end
+
+ context "when a valid validation key is given and we're doing old-style client creation" do
+ before do
+ Chef::Config[:validation_key] = "/blah"
+ allow(File).to receive(:exist?).with(%r{/blah}).and_return true
+ allow(vault_handler_mock).to receive(:doing_chef_vault?).and_return false
+ end
+
+ it "shows a warning message" do
+ expect(knife.ui).to receive(:warn).twice
+ knife.register_client
+ end
+ end
+ end
+
+ describe "#perform_bootstrap" do
+ let(:exit_status) { 0 }
+ let(:stdout) { "" }
+ let(:result_mock) { double("result", exit_status: exit_status, stderr: "A message", stdout: stdout) }
+
+ before do
+ allow(connection).to receive(:hostname).and_return "testhost"
+ end
+ it "runs the remote script and logs the output" do
+ expect(knife.ui).to receive(:info).with(/Bootstrapping.*/)
+ expect(knife).to receive(:bootstrap_command)
+ .with("/path.sh")
+ .and_return("sh /path.sh")
+ expect(connection)
+ .to receive(:run_command)
+ .with("sh /path.sh")
+ .and_yield("output here", nil)
+ .and_return result_mock
+
+ expect(knife.ui).to receive(:msg).with(/testhost/)
+ knife.perform_bootstrap("/path.sh")
+ end
+
+ context "when the remote command fails" do
+ let(:exit_status) { 1 }
+ it "shows an error and exits" do
+ expect(knife.ui).to receive(:info).with(/Bootstrapping.*/)
+ expect(knife).to receive(:bootstrap_command)
+ .with("/path.sh")
+ .and_return("sh /path.sh")
+ expect(connection).to receive(:run_command).with("sh /path.sh").and_return result_mock
+ expect { knife.perform_bootstrap("/path.sh") }.to raise_error(SystemExit)
+ end
+ end
+
+ context "when the remote command failed due to su auth error" do
+ let(:exit_status) { 1 }
+ let(:stdout) { "su: Authentication failure" }
+ let(:connection_obj) { double("connection", transport_options: {}) }
+ it "shows an error and exits" do
+ allow(connection).to receive(:connection).and_return(connection_obj)
+ expect(knife.ui).to receive(:info).with(/Bootstrapping.*/)
+ expect(knife).to receive(:bootstrap_command)
+ .with("/path.sh")
+ .and_return("su - USER -c 'sh /path.sh'")
+ expect(connection)
+ .to receive(:run_command)
+ .with("su - USER -c 'sh /path.sh'")
+ .and_yield("output here", nil)
+ .and_raise(Train::UserError)
+ expect { knife.perform_bootstrap("/path.sh") }.to raise_error(Train::UserError)
+ end
+ end
+ end
+
+ describe "#connect!" do
+ before do
+ # These are not required at run-time because train will handle its own
+ # protocol loading. In this case, we're simulating train failures and have to load
+ # them ourselves.
+ require "net/ssh"
+ require "train/transports/ssh"
+ end
+
+ context "in the normal case" do
+ it "connects using the connection_opts and notifies the operator of progress" do
+ expect(knife.ui).to receive(:info).with(/Connecting to.*/)
+ expect(knife).to receive(:connection_opts).and_return( { opts: "here" })
+ expect(knife).to receive(:do_connect).with( { opts: "here" } )
+ knife.connect!
+ end
+ end
+
+ context "when a general non-auth-failure occurs" do
+ let(:expected_error) { RuntimeError.new }
+ before do
+ allow(knife).to receive(:do_connect).and_raise(expected_error)
+ end
+ it "re-raises the exception" do
+ expect { knife.connect! }.to raise_error(expected_error)
+ end
+ end
+
+ context "when ssh fingerprint is invalid" do
+ let(:expected_error) { Train::Error.new("fingerprint AA:BB is unknown for \"blah,127.0.0.1\"") }
+ before do
+ allow(knife).to receive(:do_connect).and_raise(expected_error)
+ end
+ it "warns, prompts to accept, then connects with verify_host_key of accept_new" do
+ expect(knife).to receive(:do_connect).and_raise(expected_error)
+ expect(knife.ui).to receive(:confirm)
+ .with(/.*host 'blah \(127.0.0.1\)'.*AA:BB.*Are you sure you want to continue.*/m)
+ .and_return(true)
+ expect(knife).to receive(:do_connect) do |opts|
+ expect(opts[:verify_host_key]).to eq :accept_new
+ end
+ knife.connect!
+ end
+ end
+
+ context "when an auth failure occurs" do
+ let(:expected_error) do
+ e = Train::Error.new
+ actual = Net::SSH::AuthenticationFailed.new
+ # Simulate train's nested error - they wrap
+ # ssh/network errors in a TrainError.
+ allow(e).to receive(:cause).and_return(actual)
+ e
+ end
+
+ let(:expected_error_password_prompt) do
+ e = Train::ClientError.new
+ reason = :no_ssh_password_or_key_available
+ allow(e).to receive(:reason).and_return(reason)
+ e
+ end
+
+ let(:expected_error_password_prompt_winrm) do
+ e = RuntimeError.new
+ message = "password is a required option"
+ allow(e).to receive(:message).and_return(message)
+ e
+ end
+
+ context "and password auth was used" do
+ before do
+ allow(connection).to receive(:password_auth?).and_return true
+ end
+
+ it "re-raises the error so as not to resubmit the same failing password" do
+ expect(knife).to receive(:do_connect).and_raise(expected_error)
+ expect { knife.connect! }.to raise_error(expected_error)
+ end
+ end
+
+ context "and password auth was not used" do
+ before do
+ allow(connection).to receive(:password_auth?).and_return false
+ allow(connection).to receive(:user).and_return "testuser"
+ allow(knife).to receive(:connection_protocol).and_return connection_protocol
+ end
+
+ context "when using ssh" do
+ let(:connection_protocol) { "ssh" }
+
+ it "warns, prompts for password, then reconnects with a password-enabled configuration using the new password" do
+ expect(knife).to receive(:do_connect).and_raise(expected_error_password_prompt)
+ expect(knife.ui).to receive(:warn).with(/Failed to auth.*/)
+ expect(knife.ui).to receive(:ask).and_return("newpassword")
+ # Ensure that we set echo off to prevent showing password on the screen
+ expect(knife).to receive(:do_connect) do |opts|
+ expect(opts[:password]).to eq "newpassword"
+ end
+ knife.connect!
+ end
+ end
+
+ context "when using winrm" do
+ let(:connection_protocol) { "winrm" }
+
+ it "warns, prompts for password, then reconnects with a password-enabled configuration using the new password for" do
+ expect(knife).to receive(:do_connect).and_raise(expected_error_password_prompt_winrm)
+ expect(knife.ui).to receive(:warn).with(/Failed to auth.*/)
+ expect(knife.ui).to receive(:ask).and_return("newpassword")
+ # Ensure that we set echo off to prevent showing password on the screen
+ expect(knife).to receive(:do_connect) do |opts|
+ expect(opts[:password]).to eq "newpassword"
+ end
+ knife.connect!
+ end
+ end
+ end
+ end
+ end
+
+ it "verifies that a server to bootstrap was given as a command line arg" do
+ knife.name_args = nil
+ expect(knife).to receive(:check_license)
+ expect { knife.run }.to raise_error(SystemExit)
+ expect(stderr.string).to match(/ERROR:.+FQDN or ip/)
+ end
+
+ describe "#bootstrap_context" do
+ context "under Windows" do
+ let(:windows_test) { true }
+ it "creates a WindowsBootstrapContext" do
+ require "chef/knife/core/windows_bootstrap_context"
+ expect(knife.bootstrap_context.class).to eq Chef::Knife::Core::WindowsBootstrapContext
+ end
+ end
+
+ context "under linux" do
+ let(:linux_test) { true }
+ it "creates a BootstrapContext" do
+ require "chef/knife/core/bootstrap_context"
+ expect(knife.bootstrap_context.class).to eq Chef::Knife::Core::BootstrapContext
+ end
+ end
+ end
+
+ describe "#config_value" do
+ before do
+ knife.config[:test_key_a] = "a from cli"
+ knife.config[:test_key_b] = "b from cli"
+ Chef::Config[:knife][:test_key_a] = "a from Chef::Config"
+ Chef::Config[:knife][:test_key_c] = "c from Chef::Config"
+ Chef::Config[:knife][:alt_test_key_c] = "alt c from Chef::Config"
+ knife.merge_configs
+ Chef::Config[:treat_deprecation_warnings_as_errors] = false
+ end
+
+ it "returns the Chef::Config value from the cli when the CLI key is set" do
+ expect(knife.config_value(:test_key_a, :alt_test_key_c)).to eq "a from cli"
+ end
+
+ it "returns the Chef::Config value from the alternative key when the CLI key is not set" do
+ expect(knife.config_value(:test_key_d, :alt_test_key_c)).to eq "alt c from Chef::Config"
+ end
+
+ it "returns the default value when the key is not provided by CLI or Chef::Config" do
+ expect(knife.config_value(:missing_key, :missing_key, "found")).to eq "found"
+ end
+ end
+
+ describe "#upload_bootstrap" do
+ before do
+ allow(connection).to receive(:temp_dir).and_return(temp_dir)
+ allow(connection).to receive(:normalize_path) { |a| a }
+ end
+
+ let(:content) { "bootstrap script content" }
+ context "under Windows" do
+ let(:windows_test) { true }
+ let(:temp_dir) { "C:/Temp/bootstrap" }
+ it "creates a bat file in the temp dir provided by connection, using given content" do
+ expect(connection).to receive(:upload_file_content!).with(content, "C:/Temp/bootstrap/bootstrap.bat")
+ expect(knife.upload_bootstrap(content)).to eq "C:/Temp/bootstrap/bootstrap.bat"
+ end
+ end
+
+ context "under Linux" do
+ let(:linux_test) { true }
+ let(:temp_dir) { "/tmp/bootstrap" }
+ it "creates a 'sh file in the temp dir provided by connection, using given content" do
+ expect(connection).to receive(:upload_file_content!).with(content, "/tmp/bootstrap/bootstrap.sh")
+ expect(knife.upload_bootstrap(content)).to eq "/tmp/bootstrap/bootstrap.sh"
+ end
+ end
+ end
+
+ describe "#bootstrap_command" do
+ context "under Windows" do
+ let(:windows_test) { true }
+ it "prefixes the command to run under cmd.exe" do
+ expect(knife.bootstrap_command("autoexec.bat")).to eq "cmd.exe /C autoexec.bat"
+ end
+
+ end
+ context "under Linux" do
+ let(:linux_test) { true }
+ it "prefixes the command to run under sh" do
+ expect(knife.bootstrap_command("bootstrap.sh")).to eq "sh bootstrap.sh"
+ end
+
+ context "with --su-user option" do
+ let(:connection_obj) { double("connection", transport_options: {}) }
+ before do
+ knife.config[:su_user] = "root"
+ allow(connection).to receive(:connection).and_return(connection_obj)
+ end
+ it "prefixes the command to run using su -USER -c" do
+ expect(knife.bootstrap_command("bootstrap.sh")).to eq "su - #{knife.config[:su_user]} -c 'sh bootstrap.sh'"
+ expect(connection_obj.transport_options.key?(:pty)).to eq true
+ end
+
+ it "sudo appended if --sudo option enabled" do
+ knife.config[:use_sudo] = true
+ expect(knife.bootstrap_command("bootstrap.sh")).to eq "sudo su - #{knife.config[:su_user]} -c 'sh bootstrap.sh'"
+ expect(connection_obj.transport_options.key?(:pty)).to eq true
+ end
+ end
+ end
+ end
+
+ describe "#default_bootstrap_template" do
+ context "under Windows" do
+ let(:windows_test) { true }
+ it "is windows-chef-client-msi" do
+ expect(knife.default_bootstrap_template).to eq "windows-chef-client-msi"
+ end
+
+ end
+ context "under Linux" do
+ let(:linux_test) { true }
+ it "is chef-full" do
+ expect(knife.default_bootstrap_template).to eq "chef-full"
+ end
+ end
+ end
+
+ describe "#do_connect" do
+ let(:host_descriptor) { "example.com" }
+ let(:connection) { double("TrainConnector") }
+ let(:connector_mock) { double("TargetResolver", targets: [ connection ]) }
+ before do
+ allow(knife).to receive(:host_descriptor).and_return host_descriptor
+ end
+
+ it "creates a TrainConnector and connects it" do
+ expect(Chef::Knife::Bootstrap::TrainConnector).to receive(:new).and_return connection
+ expect(connection).to receive(:connect!)
+ knife.do_connect({})
+ end
+
+ context "when sshd configured with requiretty" do
+ let(:pty_err_msg) { "Sudo requires a TTY. Please see the README on how to configure sudo to allow for non-interactive usage." }
+ let(:expected_error) { Train::UserError.new(pty_err_msg, :sudo_no_tty) }
+ before do
+ allow(connection).to receive(:connect!).and_raise(expected_error)
+ end
+ it "retry with pty true request option" do
+ expect(Chef::Knife::Bootstrap::TrainConnector).to receive(:new).and_return(connection).exactly(2).times
+ expect(knife.ui).to receive(:warn).with("#{pty_err_msg} - trying with pty request")
+ expect { knife.do_connect({}) }.to raise_error(expected_error)
+ end
+ end
+
+ context "when a train sudo error is thrown for missing terminal" do
+ let(:ui_error_msg) { "Sudo password is required for this operation. Please enter password using -P or --ssh-password option" }
+ let(:expected_error) { Train::UserError.new(ui_error_msg, :sudo_missing_terminal) }
+ before do
+ allow(connection).to receive(:connect!).and_raise(expected_error)
+ end
+ it "outputs user friendly error message" do
+ expect { knife.do_connect({}) }.not_to raise_error
+ expect(stderr.string).to include(ui_error_msg)
+ end
+ end
+
+ end
+
+ describe "validate_winrm_transport_opts!" do
+ before do
+ allow(knife).to receive(:connection_protocol).and_return connection_protocol
+ end
+
+ context "when using ssh" do
+ let(:connection_protocol) { "ssh" }
+ it "returns true" do
+ expect(knife.validate_winrm_transport_opts!).to eq true
+ end
+ end
+ context "when using winrm" do
+ let(:connection_protocol) { "winrm" }
+ context "with plaintext auth" do
+ before do
+ knife.config[:winrm_auth_method] = "plaintext"
+ end
+ context "with ssl" do
+ before do
+ knife.config[:winrm_ssl] = true
+ end
+ it "will not error because we won't send anything in plaintext regardless" do
+ expect(knife.validate_winrm_transport_opts!).to eq true
+ end
+ end
+ context "without ssl" do
+ before do
+ knife.config[:winrm_ssl] = false
+ end
+ context "and no validation key exists" do
+ before do
+ Chef::Config[:validation_key] = "validation_key.pem"
+ allow(File).to receive(:exist?).with(/.*validation_key.pem/).and_return false
+ end
+
+ it "will error because we will generate and send a client key over the wire in plaintext" do
+ expect { knife.validate_winrm_transport_opts! }.to raise_error(SystemExit)
+ end
+
+ end
+ context "and a validation key exists" do
+ before do
+ Chef::Config[:validation_key] = "validation_key.pem"
+ allow(File).to receive(:exist?).with(/.*validation_key.pem/).and_return true
+ end
+ # TODO - don't we still send validation key?
+ it "will not error because we don not send client key over the wire" do
+ expect(knife.validate_winrm_transport_opts!).to eq true
+ end
+ end
+ end
+ end
+
+ context "with other auth" do
+ before do
+ knife.config[:winrm_auth_method] = "kerberos"
+ end
+
+ context "and no validation key exists" do
+ before do
+
+ Chef::Config[:validation_key] = "validation_key.pem"
+ allow(File).to receive(:exist?).with(/.*validation_key.pem/).and_return false
+ end
+
+ it "will not error because we're not using plaintext auth" do
+ expect(knife.validate_winrm_transport_opts!).to eq true
+ end
+ end
+ context "and a validation key exists" do
+ before do
+ Chef::Config[:validation_key] = "validation_key.pem"
+ allow(File).to receive(:exist?).with(/.*validation_key.pem/).and_return true
+ end
+
+ it "will not error because a client key won't be sent over the wire in plaintext when a validation key is present" do
+ expect(knife.validate_winrm_transport_opts!).to eq true
+ end
+ end
+
+ end
+
+ end
+
+ end
+
+ describe "#winrm_warn_no_ssl_verification" do
+ before do
+ allow(knife).to receive(:connection_protocol).and_return connection_protocol
+ end
+
+ context "when using ssh" do
+ let(:connection_protocol) { "ssh" }
+ it "does not issue a warning" do
+ expect(knife.ui).to_not receive(:warn)
+ knife.winrm_warn_no_ssl_verification
+ end
+ end
+ context "when using winrm" do
+ let(:connection_protocol) { "winrm" }
+ context "winrm_no_verify_cert is set" do
+ before do
+ knife.config[:winrm_no_verify_cert] = true
+ end
+
+ context "and ca_trust_file is present" do
+ before do
+ knife.config[:ca_trust_file] = "file"
+ end
+
+ it "does not issue a warning" do
+ expect(knife.ui).to_not receive(:warn)
+ knife.winrm_warn_no_ssl_verification
+ end
+ end
+
+ context "and winrm_ssl_peer_fingerprint is present" do
+ before do
+ knife.config[:winrm_ssl_peer_fingerprint] = "ABCD"
+ end
+ it "does not issue a warning" do
+ expect(knife.ui).to_not receive(:warn)
+ knife.winrm_warn_no_ssl_verification
+ end
+ end
+ context "and neither ca_trust_file nor winrm_ssl_peer_fingerprint is present" do
+ it "issues a warning" do
+ expect(knife.ui).to receive(:warn)
+ knife.winrm_warn_no_ssl_verification
+ end
+ end
+ end
+ end
+ end
+
+ describe "#warn_on_short_session_timeout" do
+ let(:session_timeout) { 60 }
+
+ before do
+ allow(knife).to receive(:session_timeout).and_return(session_timeout)
+ end
+
+ context "timeout is not set at all" do
+ let(:session_timeout) { nil }
+ it "does not issue a warning" do
+ expect(knife.ui).to_not receive(:warn)
+ knife.warn_on_short_session_timeout
+ end
+ end
+
+ context "timeout is more than 15" do
+ let(:session_timeout) { 16 }
+ it "does not issue a warning" do
+ expect(knife.ui).to_not receive(:warn)
+ knife.warn_on_short_session_timeout
+ end
+ end
+ context "timeout is 15 or less" do
+ let(:session_timeout) { 15 }
+ it "issues a warning" do
+ expect(knife.ui).to receive(:warn)
+ knife.warn_on_short_session_timeout
+ end
+ end
+ end
+end