summaryrefslogtreecommitdiff
path: root/spec/unit/knife
diff options
context:
space:
mode:
authorLamont Granquist <lamont@scriptkiddie.org>2015-02-09 20:31:30 -0800
committerLamont Granquist <lamont@scriptkiddie.org>2015-02-11 19:12:07 -0800
commit1aa9b128d22a21a39e6d7b7a833538ae3e15929d (patch)
tree0ff07fef992db67a7a1e899a35484fa0c6f292a7 /spec/unit/knife
parentb5243fb3a5151d65f3caa689d01a573809c441f2 (diff)
downloadchef-1aa9b128d22a21a39e6d7b7a833538ae3e15929d.tar.gz
validatorless bootstraps and chef-vault integration
Diffstat (limited to 'spec/unit/knife')
-rw-r--r--spec/unit/knife/bootstrap/chef_vault_handler_spec.rb153
-rw-r--r--spec/unit/knife/bootstrap/client_builder_spec.rb178
-rw-r--r--spec/unit/knife/bootstrap_spec.rb76
-rw-r--r--spec/unit/knife/core/bootstrap_context_spec.rb1
4 files changed, 393 insertions, 15 deletions
diff --git a/spec/unit/knife/bootstrap/chef_vault_handler_spec.rb b/spec/unit/knife/bootstrap/chef_vault_handler_spec.rb
new file mode 100644
index 0000000000..5d0c1c900c
--- /dev/null
+++ b/spec/unit/knife/bootstrap/chef_vault_handler_spec.rb
@@ -0,0 +1,153 @@
+#
+# Author:: Lamont Granquist <lamont@chef.io>)
+# Copyright:: Copyright (c) 2015 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 'spec_helper'
+
+describe Chef::Knife::Bootstrap::ChefVaultHandler do
+
+ let(:stdout) { StringIO.new }
+ let(:stderr) { StringIO.new }
+ let(:stdin) { StringIO.new }
+ let(:ui) { Chef::Knife::UI.new(stdout, stderr, stdin, {}) }
+
+ let(:knife_config) { {} }
+
+ let(:node_name) { "bevell.wat" }
+
+ let(:chef_vault_handler) {
+ chef_vault_handler = Chef::Knife::Bootstrap::ChefVaultHandler.new(knife_config: knife_config, ui: ui)
+ chef_vault_handler
+ }
+
+ context "when there's no vault option" do
+ it "should report its not doing anything" do
+ expect(chef_vault_handler.doing_chef_vault?).to be false
+ end
+
+ it "shouldn't do anything" do
+ expect(chef_vault_handler).to_not receive(:sanity_check)
+ expect(chef_vault_handler).to_not receive(:update_vault_list!)
+ chef_vault_handler
+ end
+ end
+
+ context "when setting chef vault items" do
+ let(:vault_item) { double("ChefVault::Item") }
+
+ before do
+ expect(chef_vault_handler).to receive(:wait_for_client).and_return(false)
+ expect(chef_vault_handler).to receive(:require_chef_vault!).at_least(:once)
+ expect(vault_item).to receive(:clients).with("name:#{node_name}").at_least(:once)
+ expect(vault_item).to receive(:save).at_least(:once)
+ end
+
+ context "from knife_config[:vault_item]" do
+ it "sets a single item as a scalar" do
+ knife_config[:vault_item] = { 'vault' => 'item1' }
+ expect(chef_vault_handler).to receive(:load_chef_vault_item).with('vault', 'item1').and_return(vault_item)
+ chef_vault_handler.run(node_name: node_name)
+ end
+
+ it "sets a single item as an array" do
+ knife_config[:vault_item] = { 'vault' => [ 'item1' ] }
+ expect(chef_vault_handler).to receive(:load_chef_vault_item).with('vault', 'item1').and_return(vault_item)
+ chef_vault_handler.run(node_name: node_name)
+ end
+
+ it "sets two items as an array" do
+ knife_config[:vault_item] = { 'vault' => [ 'item1', 'item2' ] }
+ expect(chef_vault_handler).to receive(:load_chef_vault_item).with('vault', 'item1').and_return(vault_item)
+ expect(chef_vault_handler).to receive(:load_chef_vault_item).with('vault', 'item2').and_return(vault_item)
+ chef_vault_handler.run(node_name: node_name)
+ end
+
+ it "sets two vaults from different hash keys" do
+ knife_config[:vault_item] = { 'vault' => [ 'item1', 'item2' ], 'vault2' => [ 'item3' ] }
+ expect(chef_vault_handler).to receive(:load_chef_vault_item).with('vault', 'item1').and_return(vault_item)
+ expect(chef_vault_handler).to receive(:load_chef_vault_item).with('vault', 'item2').and_return(vault_item)
+ expect(chef_vault_handler).to receive(:load_chef_vault_item).with('vault2', 'item3').and_return(vault_item)
+ chef_vault_handler.run(node_name: node_name)
+ end
+ end
+
+ context "from knife_config[:vault_list]" do
+ it "sets a single item as a scalar" do
+ knife_config[:vault_list] = '{ "vault": "item1" }'
+ expect(chef_vault_handler).to receive(:load_chef_vault_item).with('vault', 'item1').and_return(vault_item)
+ chef_vault_handler.run(node_name: node_name)
+ end
+
+ it "sets a single item as an array" do
+ knife_config[:vault_list] = '{ "vault": [ "item1" ] }'
+ expect(chef_vault_handler).to receive(:load_chef_vault_item).with('vault', 'item1').and_return(vault_item)
+ chef_vault_handler.run(node_name: node_name)
+ end
+
+ it "sets two items as an array" do
+ knife_config[:vault_list] = '{ "vault": [ "item1", "item2" ] }'
+ expect(chef_vault_handler).to receive(:load_chef_vault_item).with('vault', 'item1').and_return(vault_item)
+ expect(chef_vault_handler).to receive(:load_chef_vault_item).with('vault', 'item2').and_return(vault_item)
+ chef_vault_handler.run(node_name: node_name)
+ end
+
+ it "sets two vaults from different hash keys" do
+ knife_config[:vault_list] = '{ "vault": [ "item1", "item2" ], "vault2": [ "item3" ] }'
+ expect(chef_vault_handler).to receive(:load_chef_vault_item).with('vault', 'item1').and_return(vault_item)
+ expect(chef_vault_handler).to receive(:load_chef_vault_item).with('vault', 'item2').and_return(vault_item)
+ expect(chef_vault_handler).to receive(:load_chef_vault_item).with('vault2', 'item3').and_return(vault_item)
+ chef_vault_handler.run(node_name: node_name)
+ end
+ end
+
+ context "from knife_config[:vault_file]" do
+
+ def setup_file_contents(json)
+ stringio = StringIO.new(json)
+ knife_config[:vault_file] = "/foo/bar/baz"
+ expect(File).to receive(:read).with(knife_config[:vault_file]).and_return(stringio)
+ end
+
+ it "sets a single item as a scalar" do
+ setup_file_contents('{ "vault": "item1" }')
+ expect(chef_vault_handler).to receive(:load_chef_vault_item).with('vault', 'item1').and_return(vault_item)
+ chef_vault_handler.run(node_name: node_name)
+ end
+
+ it "sets a single item as an array" do
+ setup_file_contents('{ "vault": [ "item1" ] }')
+ expect(chef_vault_handler).to receive(:load_chef_vault_item).with('vault', 'item1').and_return(vault_item)
+ chef_vault_handler.run(node_name: node_name)
+ end
+
+ it "sets two items as an array" do
+ setup_file_contents('{ "vault": [ "item1", "item2" ] }')
+ expect(chef_vault_handler).to receive(:load_chef_vault_item).with('vault', 'item1').and_return(vault_item)
+ expect(chef_vault_handler).to receive(:load_chef_vault_item).with('vault', 'item2').and_return(vault_item)
+ chef_vault_handler.run(node_name: node_name)
+ end
+
+ it "sets two vaults from different hash keys" do
+ setup_file_contents('{ "vault": [ "item1", "item2" ], "vault2": [ "item3" ] }')
+ expect(chef_vault_handler).to receive(:load_chef_vault_item).with('vault', 'item1').and_return(vault_item)
+ expect(chef_vault_handler).to receive(:load_chef_vault_item).with('vault', 'item2').and_return(vault_item)
+ expect(chef_vault_handler).to receive(:load_chef_vault_item).with('vault2', 'item3').and_return(vault_item)
+ chef_vault_handler.run(node_name: node_name)
+ end
+ end
+ end
+end
diff --git a/spec/unit/knife/bootstrap/client_builder_spec.rb b/spec/unit/knife/bootstrap/client_builder_spec.rb
new file mode 100644
index 0000000000..e6aa307c7e
--- /dev/null
+++ b/spec/unit/knife/bootstrap/client_builder_spec.rb
@@ -0,0 +1,178 @@
+#
+# Author:: Lamont Granquist <lamont@chef.io>)
+# Copyright:: Copyright (c) 2015 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 'spec_helper'
+
+
+describe Chef::Knife::Bootstrap::ClientBuilder do
+
+ let(:stdout) { StringIO.new }
+ let(:stderr) { StringIO.new }
+ let(:stdin) { StringIO.new }
+ let(:ui) { Chef::Knife::UI.new(stdout, stderr, stdin, {}) }
+
+ let(:knife_config) { {} }
+
+ let(:chef_config) { {} }
+
+ let(:node_name) { "bevell.wat" }
+
+ let(:rest) { double("Chef::REST") }
+
+ let(:client_builder) {
+ client_builder = Chef::Knife::Bootstrap::ClientBuilder.new(knife_config: knife_config, chef_config: chef_config, ui: ui)
+ allow(client_builder).to receive(:rest).and_return(rest)
+ allow(client_builder).to receive(:node_name).and_return(node_name)
+ client_builder
+ }
+
+ context "#sanity_check!" do
+ let(:response_404) { OpenStruct.new(:code => '404') }
+ let(:exception_404) { Net::HTTPServerException.new("404 not found", response_404) }
+
+ context "in cases where the prompting fails" do
+ before do
+ # should fail early in #run
+ expect(client_builder).to_not receive(:create_client!)
+ expect(client_builder).to_not receive(:create_node!)
+ end
+
+ it "exits when the node exists and the user does not want to delete" do
+ expect(rest).to receive(:get_rest).with("nodes/#{node_name}")
+ expect(ui.stdin).to receive(:readline).and_return('n')
+ expect { client_builder.run }.to raise_error(SystemExit)
+ end
+
+ it "exits when the client exists and the user does not want to delete" do
+ expect(rest).to receive(:get_rest).with("nodes/#{node_name}").and_raise(exception_404)
+ expect(rest).to receive(:get_rest).with("clients/#{node_name}")
+ expect(ui.stdin).to receive(:readline).and_return('n')
+ expect { client_builder.run }.to raise_error(SystemExit)
+ end
+ end
+
+ context "in cases where the prompting succeeds" do
+ before do
+ # mock out the rest of #run
+ expect(client_builder).to receive(:create_client!)
+ expect(client_builder).to receive(:create_node!)
+ end
+
+ it "when both the client and node do not exist it succeeds" do
+ expect(rest).to receive(:get_rest).with("nodes/#{node_name}").and_raise(exception_404)
+ expect(rest).to receive(:get_rest).with("clients/#{node_name}").and_raise(exception_404)
+ expect { client_builder.run }.not_to raise_error
+ end
+
+ it "when we are allowed to delete an old node" do
+ expect(rest).to receive(:get_rest).with("nodes/#{node_name}")
+ expect(ui.stdin).to receive(:readline).and_return('y')
+ expect(rest).to receive(:get_rest).with("clients/#{node_name}").and_raise(exception_404)
+ expect(rest).to receive(:delete).with("nodes/#{node_name}")
+ expect { client_builder.run }.not_to raise_error
+ end
+
+ it "when we are allowed to delete an old client" do
+ expect(rest).to receive(:get_rest).with("nodes/#{node_name}").and_raise(exception_404)
+ expect(rest).to receive(:get_rest).with("clients/#{node_name}")
+ expect(ui.stdin).to receive(:readline).and_return('y')
+ expect(rest).to receive(:delete).with("clients/#{node_name}")
+ expect { client_builder.run }.not_to raise_error
+ end
+
+ it "when we are are allowed to delete both an old client and node" do
+ expect(rest).to receive(:get_rest).with("nodes/#{node_name}")
+ expect(rest).to receive(:get_rest).with("clients/#{node_name}")
+ expect(ui.stdin).to receive(:readline).twice.and_return('y')
+ expect(rest).to receive(:delete).with("nodes/#{node_name}")
+ expect(rest).to receive(:delete).with("clients/#{node_name}")
+ expect { client_builder.run }.not_to raise_error
+ end
+ end
+ end
+
+ context "#create_client!" do
+ before do
+ # mock out the rest of #run
+ expect(client_builder).to receive(:sanity_check)
+ expect(client_builder).to receive(:create_node!)
+ end
+
+ it "delegates everything to Chef::ApiClient::Registration" do
+ reg_double = double("Chef::ApiClient::Registration")
+ expect(Chef::ApiClient::Registration).to receive(:new).with(node_name, client_builder.client_path, http_api: rest).and_return(reg_double)
+ expect(reg_double).to receive(:run)
+ client_builder.run
+ end
+
+ end
+
+ context "#client_path" do
+ it "has a public API for the temporary client.pem file" do
+ expect(client_builder.client_path).to match(/#{node_name}.pem/)
+ end
+ end
+
+ context "#create_node!" do
+ before do
+ # mock out the rest of #run
+ expect(client_builder).to receive(:sanity_check)
+ expect(client_builder).to receive(:create_client!)
+ # mock out default node building steps
+ expect(client_builder).to receive(:client_rest).and_return(client_rest)
+ expect(Chef::Node).to receive(:new).with(chef_server_rest: client_rest).and_return(node)
+ expect(node).to receive(:name).with(node_name)
+ expect(node).to receive(:save)
+ end
+
+ let(:client_rest) { double("Chef::REST (client)") }
+
+ let(:node) { double("Chef::Node") }
+
+ it "builds a node with a default run_list of []" do
+ expect(node).to receive(:run_list).with([])
+ client_builder.run
+ end
+
+ it "builds a node when the run_list is a string" do
+ knife_config[:run_list] = "role[base],role[app]"
+ expect(node).to receive(:run_list).with(["role[base]", "role[app]"])
+ client_builder.run
+ end
+
+ it "builds a node when the run_list is an Array" do
+ knife_config[:run_list] = ["role[base]", "role[app]"]
+ expect(node).to receive(:run_list).with(["role[base]", "role[app]"])
+ client_builder.run
+ end
+
+ it "builds a node with first_boot_attributes if they're given" do
+ knife_config[:first_boot_attributes] = {:baz => :quux}
+ expect(node).to receive(:normal_attrs=).with({:baz=>:quux})
+ expect(node).to receive(:run_list).with([])
+ client_builder.run
+ end
+
+ it "builds a node with an environment if its given" do
+ knife_config[:environment] = "production"
+ expect(node).to receive(:environment).with("production")
+ expect(node).to receive(:run_list).with([])
+ client_builder.run
+ end
+ end
+end
diff --git a/spec/unit/knife/bootstrap_spec.rb b/spec/unit/knife/bootstrap_spec.rb
index 4cb681fdbe..4743ea315e 100644
--- a/spec/unit/knife/bootstrap_spec.rb
+++ b/spec/unit/knife/bootstrap_spec.rb
@@ -520,25 +520,71 @@ describe Chef::Knife::Bootstrap do
knife_ssh
end
- it "configures the underlying ssh command and then runs it" do
- expect(knife_ssh).to receive(:run)
- knife.run
- end
+ context "when running with a configured and present validation key" do
+ before do
+ # this tests runs the old code path where we have a validation key, so we need to pass that check
+ allow(File).to receive(:exist?).with(File.expand_path(Chef::Config[:validation_key])).and_return(true)
+ end
+
+
+ it "configures the underlying ssh command and then runs it" do
+ expect(knife_ssh).to receive(:run)
+ knife.run
+ end
+
+ it "falls back to password based auth when auth fails the first time" do
+ allow(knife).to receive(:puts)
+
+ fallback_knife_ssh = knife_ssh.dup
+ expect(knife_ssh).to receive(:run).and_raise(Net::SSH::AuthenticationFailed.new("no ssh for you"))
+ allow(knife).to receive(:knife_ssh_with_password_auth).and_return(fallback_knife_ssh)
+ expect(fallback_knife_ssh).to receive(:run)
+ knife.run
+ end
+
+ it "raises the exception if config[:ssh_password] is set and an authentication exception is raised" do
+ knife.config[:ssh_password] = "password"
+ expect(knife_ssh).to receive(:run).and_raise(Net::SSH::AuthenticationFailed)
+ expect { knife.run }.to raise_error(Net::SSH::AuthenticationFailed)
+ end
- it "falls back to password based auth when auth fails the first time" do
- allow(knife).to receive(:puts)
+ it "creates the client and adds chef-vault items if vault_list is set" do
+ knife.config[:vault_file] = "/not/our/responsibility/to/check/if/this/exists"
+ expect(knife_ssh).to receive(:run)
+ expect(knife.client_builder).to receive(:run)
+ expect(knife.chef_vault_handler).to receive(:run).with(node_name: knife.config[:chef_node_name])
+ knife.run
+ end
+
+ it "creates the client and adds chef-vault items if vault_items is set" do
+ knife.config[:vault_list] = '{ "vault" => "item" }'
+ expect(knife_ssh).to receive(:run)
+ expect(knife.client_builder).to receive(:run)
+ expect(knife.chef_vault_handler).to receive(:run).with(node_name: knife.config[:chef_node_name])
+ knife.run
+ end
- fallback_knife_ssh = knife_ssh.dup
- expect(knife_ssh).to receive(:run).and_raise(Net::SSH::AuthenticationFailed.new("no ssh for you"))
- allow(knife).to receive(:knife_ssh_with_password_auth).and_return(fallback_knife_ssh)
- expect(fallback_knife_ssh).to receive(:run)
- knife.run
+ it "does old-style validation without creating a client key if vault_list+vault_items is not set" do
+ expect(File).to receive(:exist?).with(File.expand_path(Chef::Config[:validation_key])).and_return(true)
+ expect(knife_ssh).to receive(:run)
+ expect(knife.client_builder).not_to receive(:run)
+ expect(knife.chef_vault_handler).not_to receive(:run).with(node_name: knife.config[:chef_node_name])
+ knife.run
+ end
end
- it "raises the exception if config[:ssh_password] is set and an authentication exception is raised" do
- knife.config[:ssh_password] = "password"
- expect(knife_ssh).to receive(:run).and_raise(Net::SSH::AuthenticationFailed)
- expect { knife.run }.to raise_error(Net::SSH::AuthenticationFailed)
+ context "when the validation key is not present" do
+ before do
+ # this tests runs the old code path where we have a validation key, so we need to pass that check
+ allow(File).to receive(:exist?).with(File.expand_path(Chef::Config[:validation_key])).and_return(false)
+ end
+
+ it "creates the client (and possibly adds chef-vault items)" do
+ expect(knife_ssh).to receive(:run)
+ expect(knife.client_builder).to receive(:run)
+ expect(knife.chef_vault_handler).to receive(:run).with(node_name: knife.config[:chef_node_name])
+ knife.run
+ end
end
end
diff --git a/spec/unit/knife/core/bootstrap_context_spec.rb b/spec/unit/knife/core/bootstrap_context_spec.rb
index e7d2464fa1..3718cb228c 100644
--- a/spec/unit/knife/core/bootstrap_context_spec.rb
+++ b/spec/unit/knife/core/bootstrap_context_spec.rb
@@ -77,6 +77,7 @@ EXPECTED
describe "validation key path that contains a ~" do
let(:chef_config){ {:validation_key => '~/my.key'} }
it "reads the validation key when it contains a ~" do
+ expect(File).to receive(:exist?).with(File.expand_path("my.key", ENV['HOME'])).and_return(true)
expect(IO).to receive(:read).with(File.expand_path("my.key", ENV['HOME']))
bootstrap_context.validation_key
end