diff options
author | Lamont Granquist <lamont@scriptkiddie.org> | 2015-02-09 20:31:30 -0800 |
---|---|---|
committer | Lamont Granquist <lamont@scriptkiddie.org> | 2015-02-11 19:12:07 -0800 |
commit | 1aa9b128d22a21a39e6d7b7a833538ae3e15929d (patch) | |
tree | 0ff07fef992db67a7a1e899a35484fa0c6f292a7 /spec/unit/knife | |
parent | b5243fb3a5151d65f3caa689d01a573809c441f2 (diff) | |
download | chef-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.rb | 153 | ||||
-rw-r--r-- | spec/unit/knife/bootstrap/client_builder_spec.rb | 178 | ||||
-rw-r--r-- | spec/unit/knife/bootstrap_spec.rb | 76 | ||||
-rw-r--r-- | spec/unit/knife/core/bootstrap_context_spec.rb | 1 |
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 |