diff options
Diffstat (limited to 'spec/unit/knife')
61 files changed, 6359 insertions, 0 deletions
diff --git a/spec/unit/knife/bootstrap_spec.rb b/spec/unit/knife/bootstrap_spec.rb new file mode 100644 index 0000000000..b2247bb5a4 --- /dev/null +++ b/spec/unit/knife/bootstrap_spec.rb @@ -0,0 +1,214 @@ +# +# Author:: Ian Meyer (<ianmmeyer@gmail.com>) +# Copyright:: Copyright (c) 2010 Ian Meyer +# 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' + +Chef::Knife::Bootstrap.load_deps +require 'net/ssh' + +describe Chef::Knife::Bootstrap do + before(:each) do + Chef::Log.logger = Logger.new(StringIO.new) + @knife = Chef::Knife::Bootstrap.new + @knife.config[:template_file] = File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "test.erb")) + @stdout = StringIO.new + @knife.ui.stub!(:stdout).and_return(@stdout) + @stderr = StringIO.new + @knife.ui.stub!(:stderr).and_return(@stderr) + end + + it "should return a name of default bootstrap template" do + @knife.find_template.should be_a_kind_of(String) + end + + it "should error if template can not be found" do + @knife.config[:template_file] = false + @knife.config[:distro] = 'penultimate' + lambda { @knife.find_template }.should raise_error + end + + it "should look for templates early in the run" do + File.stub(:exists?).and_return(true) + @knife.name_args = ['shatner'] + @knife.stub!(:read_template).and_return("") + @knife.stub!(:knife_ssh).and_return(true) + @knife_ssh = @knife.knife_ssh + @knife.should_receive(:find_template).ordered + @knife.should_receive(:knife_ssh).ordered + @knife_ssh.should_receive(:run) # rspec appears to keep order per object + @knife.run + end + + it "should load the specified template" do + @knife.config[:distro] = 'fedora13-gems' + lambda { @knife.find_template }.should_not raise_error + end + + it "should load the specified template from a Ruby gem" do + @knife.config[:template_file] = false + Gem.stub(:find_files).and_return(["/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"]) + File.stub(:exists?).and_return(true) + IO.stub(:read).and_return('random content') + @knife.config[:distro] = 'fake-bootstrap-template' + lambda { @knife.find_template }.should_not raise_error + end + + it "should return an empty run_list" do + @knife.instance_variable_set("@template_file", @knife.config[:template_file]) + template_string = @knife.read_template + @knife.render_template(template_string).should == '{"run_list":[]}' + end + + it "should have role[base] in the run_list" do + @knife.instance_variable_set("@template_file", @knife.config[:template_file]) + template_string = @knife.read_template + @knife.parse_options(["-r","role[base]"]) + @knife.render_template(template_string).should == '{"run_list":["role[base]"]}' + end + + it "should have role[base] and recipe[cupcakes] in the run_list" do + @knife.instance_variable_set("@template_file", @knife.config[:template_file]) + template_string = @knife.read_template + @knife.parse_options(["-r", "role[base],recipe[cupcakes]"]) + @knife.render_template(template_string).should == '{"run_list":["role[base]","recipe[cupcakes]"]}' + end + + it "should have foo => {bar => baz} in the first_boot" do + @knife.instance_variable_set("@template_file", @knife.config[:template_file]) + template_string = @knife.read_template + @knife.parse_options(["-j", '{"foo":{"bar":"baz"}}']) + expected_hash = Yajl::Parser.new.parse('{"foo":{"bar":"baz"},"run_list":[]}') + actual_hash = Yajl::Parser.new.parse(@knife.render_template(template_string)) + actual_hash.should == expected_hash + end + + it "should create a hint file when told to" do + @knife.config[:template_file] = File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "test-hints.erb")) + @knife.instance_variable_set("@template_file", @knife.config[:template_file]) + template_string = @knife.read_template + @knife.parse_options(["--hint", "openstack"]) + @knife.render_template(template_string).should match /\/etc\/chef\/ohai\/hints\/openstack.json/ + end + + it "should populate a hint file with JSON when given a file to read" do + @knife.stub(:find_template).and_return(true) + @knife.config[:template_file] = File.expand_path(File.join(CHEF_SPEC_DATA, "bootstrap", "test-hints.erb")) + ::File.stub!(:read).and_return('{ "foo" : "bar" }') + @knife.instance_variable_set("@template_file", @knife.config[:template_file]) + template_string = @knife.read_template + @knife.stub!(:read_template).and_return('{ "foo" : "bar" }') + @knife.parse_options(["--hint", "openstack=hints/openstack.json"]) + @knife.render_template(template_string).should match /\{\"foo\":\"bar\"\}/ + end + + + it "should take the node name from ARGV" do + @knife.name_args = ['barf'] + @knife.name_args.first.should == "barf" + end + + describe "when configuring the underlying knife ssh command" do + before do + @knife.name_args = ["foo.example.com"] + @knife.config[:ssh_user] = "rooty" + @knife.config[:ssh_password] = "open_sesame" + Chef::Config[:knife][:ssh_port] = "4001" + @knife.config[:identity_file] = "~/.ssh/me.rsa" + @knife.stub!(:read_template).and_return("") + @knife_ssh = @knife.knife_ssh + end + + it "configures the hostname" do + @knife_ssh.name_args.first.should == "foo.example.com" + end + + it "configures the ssh user" do + @knife_ssh.config[:ssh_user].should == 'rooty' + end + + it "configures the ssh password" do + @knife_ssh.config[:ssh_password].should == 'open_sesame' + end + + it "configures the ssh port" do + @knife_ssh.config[:ssh_port].should == '4001' + end + + it "configures the ssh identity file" do + @knife_ssh.config[:identity_file].should == '~/.ssh/me.rsa' + end + end + + describe "when falling back to password auth when host key auth fails" do + before do + @knife.name_args = ["foo.example.com"] + @knife.config[:ssh_user] = "rooty" + @knife.config[:identity_file] = "~/.ssh/me.rsa" + @knife.stub!(:read_template).and_return("") + @knife_ssh = @knife.knife_ssh + end + + it "prompts the user for a password " do + @knife.stub!(:knife_ssh).and_return(@knife_ssh) + @knife_ssh.stub!(:get_password).and_return('typed_in_password') + alternate_knife_ssh = @knife.knife_ssh_with_password_auth + alternate_knife_ssh.config[:ssh_password].should == 'typed_in_password' + end + + it "configures knife not to use the identity file that didn't work previously" do + @knife.stub!(:knife_ssh).and_return(@knife_ssh) + @knife_ssh.stub!(:get_password).and_return('typed_in_password') + alternate_knife_ssh = @knife.knife_ssh_with_password_auth + alternate_knife_ssh.config[:identity_file].should be_nil + end + end + + describe "when running the bootstrap" do + before do + @knife.name_args = ["foo.example.com"] + @knife.config[:ssh_user] = "rooty" + @knife.config[:identity_file] = "~/.ssh/me.rsa" + @knife.stub!(:read_template).and_return("") + @knife_ssh = @knife.knife_ssh + @knife.stub!(:knife_ssh).and_return(@knife_ssh) + end + + it "verifies that a server to bootstrap was given as a command line arg" do + @knife.name_args = nil + lambda { @knife.run }.should raise_error(SystemExit) + @stderr.string.should match /ERROR:.+FQDN or ip/ + end + + it "configures the underlying ssh command and then runs it" do + @knife_ssh.should_receive(:run) + @knife.run + end + + it "falls back to password based auth when auth fails the first time" do + @knife.stub!(:puts) + + @fallback_knife_ssh = @knife_ssh.dup + @knife_ssh.should_receive(:run).and_raise(Net::SSH::AuthenticationFailed.new("no ssh for you")) + @knife.stub!(:knife_ssh_with_password_auth).and_return(@fallback_knife_ssh) + @fallback_knife_ssh.should_receive(:run) + @knife.run + end + + end + +end diff --git a/spec/unit/knife/client_bulk_delete_spec.rb b/spec/unit/knife/client_bulk_delete_spec.rb new file mode 100644 index 0000000000..55351554d1 --- /dev/null +++ b/spec/unit/knife/client_bulk_delete_spec.rb @@ -0,0 +1,78 @@ +# +# Author:: Stephen Delano (<stephen@opscode.com>) +# Copyright:: Copyright (c) 2010 Opscode, 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::ClientBulkDelete do + before(:each) do + Chef::Log.logger = Logger.new(StringIO.new) + + Chef::Config[:node_name] = "webmonkey.example.com" + @knife = Chef::Knife::ClientBulkDelete.new + @knife.name_args = ["."] + @stdout = StringIO.new + @knife.ui.stub!(:stdout).and_return(@stdout) + @knife.ui.stub!(:confirm).and_return(true) + @clients = Hash.new + %w{tim dan stephen}.each do |client_name| + client = Chef::ApiClient.new() + client.name(client_name) + client.stub!(:destroy).and_return(true) + @clients[client_name] = client + end + Chef::ApiClient.stub!(:list).and_return(@clients) + end + + describe "run" do + + it "should get the list of the clients" do + Chef::ApiClient.should_receive(:list).and_return(@clients) + @knife.run + end + + it "should print the clients you are about to delete" do + @knife.run + @stdout.string.should match(/#{@knife.ui.list(@clients.keys.sort, :columns_down)}/) + end + + it "should confirm you really want to delete them" do + @knife.ui.should_receive(:confirm) + @knife.run + end + + it "should delete each client" do + @clients.each_value do |c| + c.should_receive(:destroy) + end + @knife.run + end + + it "should only delete clients that match the regex" do + @knife.name_args = ["tim"] + @clients["tim"].should_receive(:destroy) + @clients["stephen"].should_not_receive(:destroy) + @clients["dan"].should_not_receive(:destroy) + @knife.run + end + + it "should exit if the regex is not provided" do + @knife.name_args = [] + lambda { @knife.run }.should raise_error(SystemExit) + end + end +end diff --git a/spec/unit/knife/client_create_spec.rb b/spec/unit/knife/client_create_spec.rb new file mode 100644 index 0000000000..c049748074 --- /dev/null +++ b/spec/unit/knife/client_create_spec.rb @@ -0,0 +1,74 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Copyright:: Copyright (c) 2008 Opscode, 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' + +Chef::Knife::ClientCreate.load_deps + +describe Chef::Knife::ClientCreate do + before(:each) do + Chef::Config[:node_name] = "webmonkey.example.com" + @knife = Chef::Knife::ClientCreate.new + @knife.config = { + :file => nil + } + @knife.name_args = [ "adam" ] + @client = Chef::ApiClient.new + @client.stub!(:save).and_return({ 'private_key' => '' }) + @knife.stub!(:edit_data).and_return(@client) + @knife.stub!(:puts) + Chef::ApiClient.stub!(:new).and_return(@client) + @stdout = StringIO.new + @knife.ui.stub!(:stdout).and_return(@stdout) + end + + describe "run" do + it "should create a new Client" do + Chef::ApiClient.should_receive(:new).and_return(@client) + @knife.run + @stdout.string.should match /created client.+adam/i + end + + it "should set the Client name" do + @client.should_receive(:name).with("adam") + @knife.run + end + + it "should allow you to edit the data" do + @knife.should_receive(:edit_data).with(@client) + @knife.run + end + + it "should save the Client" do + @client.should_receive(:save) + @knife.run + end + + describe "with -f or --file" do + it "should write the private key to a file" do + @knife.config[:file] = "/tmp/monkeypants" + @client.stub!(:save).and_return({ 'private_key' => "woot" }) + filehandle = mock("Filehandle") + filehandle.should_receive(:print).with('woot') + File.should_receive(:open).with("/tmp/monkeypants", "w").and_yield(filehandle) + @knife.run + end + end + + end +end diff --git a/spec/unit/knife/client_delete_spec.rb b/spec/unit/knife/client_delete_spec.rb new file mode 100644 index 0000000000..865f19f713 --- /dev/null +++ b/spec/unit/knife/client_delete_spec.rb @@ -0,0 +1,40 @@ +# +# Author:: Thomas Bishop (<bishop.thomas@gmail.com>) +# Copyright:: Copyright (c) 2011 Thomas Bishop +# 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::ClientDelete do + before(:each) do + @knife = Chef::Knife::ClientDelete.new + @knife.name_args = [ 'adam' ] + end + + describe 'run' do + it 'should delete the client' do + @knife.should_receive(:delete_object).with(Chef::ApiClient, 'adam') + @knife.run + end + + it 'should print usage and exit when a client name is not provided' do + @knife.name_args = [] + @knife.should_receive(:show_usage) + @knife.ui.should_receive(:fatal) + lambda { @knife.run }.should raise_error(SystemExit) + end + end +end diff --git a/spec/unit/knife/client_edit_spec.rb b/spec/unit/knife/client_edit_spec.rb new file mode 100644 index 0000000000..1308d14fd5 --- /dev/null +++ b/spec/unit/knife/client_edit_spec.rb @@ -0,0 +1,40 @@ +# +# Author:: Thomas Bishop (<bishop.thomas@gmail.com>) +# Copyright:: Copyright (c) 2011 Thomas Bishop +# 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::ClientEdit do + before(:each) do + @knife = Chef::Knife::ClientEdit.new + @knife.name_args = [ 'adam' ] + end + + describe 'run' do + it 'should edit the client' do + @knife.should_receive(:edit_object).with(Chef::ApiClient, 'adam') + @knife.run + end + + it 'should print usage and exit when a client name is not provided' do + @knife.name_args = [] + @knife.should_receive(:show_usage) + @knife.ui.should_receive(:fatal) + lambda { @knife.run }.should raise_error(SystemExit) + end + end +end diff --git a/spec/unit/knife/client_list_spec.rb b/spec/unit/knife/client_list_spec.rb new file mode 100644 index 0000000000..6237a0d14c --- /dev/null +++ b/spec/unit/knife/client_list_spec.rb @@ -0,0 +1,34 @@ +# +# Author:: Thomas Bishop (<bishop.thomas@gmail.com>) +# Copyright:: Copyright (c) 2011 Thomas Bishop +# 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::ClientList do + before(:each) do + @knife = Chef::Knife::ClientList.new + @knife.name_args = [ 'adam' ] + end + + describe 'run' do + it 'should list the clients' do + Chef::ApiClient.should_receive(:list) + @knife.should_receive(:format_list_for_display) + @knife.run + end + end +end diff --git a/spec/unit/knife/client_reregister_spec.rb b/spec/unit/knife/client_reregister_spec.rb new file mode 100644 index 0000000000..0d284c0f58 --- /dev/null +++ b/spec/unit/knife/client_reregister_spec.rb @@ -0,0 +1,61 @@ +# +# Author:: Thomas Bishop (<bishop.thomas@gmail.com>) +# Copyright:: Copyright (c) 2011 Thomas Bishop +# 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::ClientReregister do + before(:each) do + @knife = Chef::Knife::ClientReregister.new + @knife.name_args = [ 'adam' ] + @client_mock = mock('client_mock') + @client_mock.stub!(:save).and_return({ 'private_key' => 'foo_key' }) + Chef::ApiClient.stub!(:load).and_return(@client_mock) + @stdout = StringIO.new + @knife.ui.stub!(:stdout).and_return(@stdout) + end + + describe 'run' do + it 'should load and save the client' do + Chef::ApiClient.should_receive(:load).with('adam').and_return(@client_mock) + @client_mock.should_receive(:save).with(true).and_return({'private_key' => 'foo_key'}) + @knife.run + end + + it 'should output the private key' do + @knife.run + @stdout.string.should match /foo_key/ + end + + it 'should print usage and exit when a client name is not provided' do + @knife.name_args = [] + @knife.should_receive(:show_usage) + @knife.ui.should_receive(:fatal) + lambda { @knife.run }.should raise_error(SystemExit) + end + + describe 'with -f or --file' do + it 'should write the private key to a file' do + @knife.config[:file] = '/tmp/monkeypants' + filehandle = mock('Filehandle') + filehandle.should_receive(:print).with('foo_key') + File.should_receive(:open).with('/tmp/monkeypants', 'w').and_yield(filehandle) + @knife.run + end + end + end +end diff --git a/spec/unit/knife/client_show_spec.rb b/spec/unit/knife/client_show_spec.rb new file mode 100644 index 0000000000..5bac3b4af6 --- /dev/null +++ b/spec/unit/knife/client_show_spec.rb @@ -0,0 +1,42 @@ +# +# Author:: Thomas Bishop (<bishop.thomas@gmail.com>) +# Copyright:: Copyright (c) 2011 Thomas Bishop +# 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::ClientShow do + before(:each) do + @knife = Chef::Knife::ClientShow.new + @knife.name_args = [ 'adam' ] + @client_mock = mock('client_mock') + end + + describe 'run' do + it 'should list the client' do + Chef::ApiClient.should_receive(:load).with('adam').and_return(@client_mock) + @knife.should_receive(:format_for_display).with(@client_mock) + @knife.run + end + + it 'should print usage and exit when a client name is not provided' do + @knife.name_args = [] + @knife.should_receive(:show_usage) + @knife.ui.should_receive(:fatal) + lambda { @knife.run }.should raise_error(SystemExit) + end + end +end diff --git a/spec/unit/knife/config_file_selection_spec.rb b/spec/unit/knife/config_file_selection_spec.rb new file mode 100644 index 0000000000..3ee18d82d0 --- /dev/null +++ b/spec/unit/knife/config_file_selection_spec.rb @@ -0,0 +1,118 @@ +# +# Author:: Nicolas Vinot (<aeris@imirhil.fr>) +# Copyright:: Copyright (c) 2010 Opscode, 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 File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') +require 'tmpdir' + +describe Chef::Knife do + before :each do + Chef::Config.stub!(:from_file).and_return(true) + end + + it "configure knife from KNIFE_HOME env variable" do + env_config = File.expand_path(File.join(Dir.tmpdir, 'knife.rb')) + File.stub!(:exist?).and_return(false) + File.stub!(:exist?).with(env_config).and_return(true) + + ENV['KNIFE_HOME'] = Dir.tmpdir + @knife = Chef::Knife.new + @knife.configure_chef + @knife.config[:config_file].should == env_config + end + + it "configure knife from PWD" do + pwd_config = "#{Dir.pwd}/knife.rb" + File.stub!(:exist?).and_return do | arg | + [ pwd_config ].include? arg + end + + @knife = Chef::Knife.new + @knife.configure_chef + @knife.config[:config_file].should == pwd_config + end + + it "configure knife from UPWARD" do + upward_dir = File.expand_path "#{Dir.pwd}/.chef" + upward_config = File.expand_path "#{upward_dir}/knife.rb" + File.stub!(:exist?).and_return do | arg | + [ upward_config ].include? arg + end + Chef::Knife.stub!(:chef_config_dir).and_return(upward_dir) + + @knife = Chef::Knife.new + @knife.configure_chef + @knife.config[:config_file].should == upward_config + end + + it "configure knife from HOME" do + home_config = File.expand_path(File.join("#{ENV['HOME']}", "/.chef/knife.rb")) + File.stub!(:exist?).and_return do | arg | + [ home_config ].include? arg + end + + @knife = Chef::Knife.new + @knife.configure_chef + @knife.config[:config_file].should == home_config + end + + it "configure knife from nothing" do + ::File.stub!(:exist?).and_return(false) + @knife = Chef::Knife.new + @knife.ui.should_receive(:warn).with("No knife configuration file found") + @knife.configure_chef + @knife.config[:config_file].should be_nil + end + + it "configure knife precedence" do + env_config = File.join(Dir.tmpdir, 'knife.rb') + pwd_config = "#{Dir.pwd}/knife.rb" + upward_dir = File.expand_path "#{Dir.pwd}/.chef" + upward_config = File.expand_path "#{upward_dir}/knife.rb" + home_config = File.expand_path(File.join("#{ENV['HOME']}", "/.chef/knife.rb")) + configs = [ env_config, pwd_config, upward_config, home_config ] + File.stub!(:exist?).and_return do | arg | + configs.include? arg + end + Chef::Knife.stub!(:chef_config_dir).and_return(upward_dir) + ENV['KNIFE_HOME'] = Dir.tmpdir + + @knife = Chef::Knife.new + @knife.configure_chef + @knife.config[:config_file].should == env_config + + configs.delete env_config + @knife.config.delete :config_file + @knife.configure_chef + @knife.config[:config_file].should == pwd_config + + configs.delete pwd_config + @knife.config.delete :config_file + @knife.configure_chef + @knife.config[:config_file].should == upward_config + + configs.delete upward_config + @knife.config.delete :config_file + @knife.configure_chef + @knife.config[:config_file].should == home_config + + configs.delete home_config + @knife.config.delete :config_file + @knife.configure_chef + @knife.config[:config_file].should be_nil + end +end diff --git a/spec/unit/knife/configure_client_spec.rb b/spec/unit/knife/configure_client_spec.rb new file mode 100644 index 0000000000..ba832103bc --- /dev/null +++ b/spec/unit/knife/configure_client_spec.rb @@ -0,0 +1,83 @@ +# +# Author:: Thomas Bishop (<bishop.thomas@gmail.com>) +# Copyright:: Copyright (c) 2011 Thomas Bishop +# 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::ConfigureClient do + before do + @knife = Chef::Knife::ConfigureClient.new + Chef::Config[:chef_server_url] = 'https://chef.example.com' + Chef::Config[:validation_client_name] = 'chef-validator' + Chef::Config[:validation_key] = '/etc/chef/validation.pem' + + @stdout = StringIO.new + @knife.ui.stub!(:stdout).and_return(@stdout) + end + + describe 'run' do + it 'should print usage and exit when a directory is not provided' do + @knife.should_receive(:show_usage) + @knife.ui.should_receive(:fatal).with(/must provide the directory/) + lambda { + @knife.run + }.should raise_error SystemExit + end + + describe 'when specifing a directory' do + before do + @knife.name_args = ['/home/bob/.chef'] + @client_file = StringIO.new + @validation_file = StringIO.new + File.should_receive(:open).with('/home/bob/.chef/client.rb', 'w'). + and_yield(@client_file) + File.should_receive(:open).with('/home/bob/.chef/validation.pem', 'w'). + and_yield(@validation_file) + IO.should_receive(:read).and_return('foo_bar_baz') + end + + it 'should recursively create the directory' do + FileUtils.should_receive(:mkdir_p).with('/home/bob/.chef') + @knife.run + end + + it 'should write out the config file' do + FileUtils.stub!(:mkdir_p) + @knife.run + @client_file.string.should match /log_level\s+\:info/ + @client_file.string.should match /log_location\s+STDOUT/ + @client_file.string.should match /chef_server_url\s+'https\:\/\/chef\.example\.com'/ + @client_file.string.should match /validation_client_name\s+'chef-validator'/ + end + + it 'should write out the validation.pem file' do + FileUtils.stub!(:mkdir_p) + @knife.run + @validation_file.string.should match /foo_bar_baz/ + end + + it 'should print information on what is being configured' do + FileUtils.stub!(:mkdir_p) + @knife.run + @stdout.string.should match /creating client configuration/i + @stdout.string.should match /writing client\.rb/i + @stdout.string.should match /writing validation\.pem/i + end + end + end + +end diff --git a/spec/unit/knife/configure_spec.rb b/spec/unit/knife/configure_spec.rb new file mode 100644 index 0000000000..85ee996dd5 --- /dev/null +++ b/spec/unit/knife/configure_spec.rb @@ -0,0 +1,229 @@ +require 'spec_helper' + +describe Chef::Knife::Configure do + before do + @original_config = Chef::Config.configuration.dup + + Chef::Log.logger = Logger.new(StringIO.new) + + Chef::Config[:node_name] = "webmonkey.example.com" + @knife = Chef::Knife::Configure.new + @rest_client = mock("null rest client", :post_rest => { :result => :true }) + @knife.stub!(:rest).and_return(@rest_client) + + @out = StringIO.new + @knife.ui.stub!(:stdout).and_return(@out) + @knife.config[:config_file] = '/home/you/.chef/knife.rb' + + @in = StringIO.new("\n" * 7) + @knife.ui.stub!(:stdin).and_return(@in) + + @err = StringIO.new + @knife.ui.stub!(:stderr).and_return(@err) + + @ohai = Ohai::System.new + @ohai.stub(:require_plugin) + @ohai[:fqdn] = "foo.example.org" + Ohai::System.stub!(:new).and_return(@ohai) + end + + after do + Chef::Config.configuration.replace(@original_config) + end + + it "asks the user for the URL of the chef server" do + @knife.ask_user_for_config + @out.string.should match(Regexp.escape('Please enter the chef server URL: [http://foo.example.org:4000]')) + @knife.chef_server.should == 'http://foo.example.org:4000' + end + + it "asks the user for the clientname they want for the new client if -i is specified" do + @knife.config[:initial] = true + Etc.stub!(:getlogin).and_return("a-new-user") + @knife.ask_user_for_config + @out.string.should match(Regexp.escape("Please enter a clientname for the new client: [a-new-user]")) + @knife.new_client_name.should == Etc.getlogin + end + + it "should not ask the user for the clientname they want for the new client if -i and --node_name are specified" do + @knife.config[:initial] = true + @knife.config[:node_name] = 'testnode' + Etc.stub!(:getlogin).and_return("a-new-user") + @knife.ask_user_for_config + @out.string.should_not match(Regexp.escape("Please enter a clientname for the new client")) + @knife.new_client_name.should == 'testnode' + end + + it "asks the user for the existing API username or clientname if -i is not specified" do + Etc.stub!(:getlogin).and_return("a-new-user") + @knife.ask_user_for_config + @out.string.should match(Regexp.escape("Please enter an existing username or clientname for the API: [a-new-user]")) + @knife.new_client_name.should == Etc.getlogin + end + + it "asks the user for the existing admin client's name if -i is specified" do + @knife.config[:initial] = true + @knife.ask_user_for_config + @out.string.should match(Regexp.escape("Please enter the existing admin clientname: [chef-webui]")) + @knife.admin_client_name.should == 'chef-webui' + end + + it "should not ask the user for the existing admin client's name if -i and --admin-client_name are specified" do + @knife.config[:initial] = true + @knife.config[:admin_client_name] = 'my-webui' + @knife.ask_user_for_config + @out.string.should_not match(Regexp.escape("Please enter the existing admin clientname:")) + @knife.admin_client_name.should == 'my-webui' + end + + it "should not ask the user for the existing admin client's name if -i is not specified" do + @knife.ask_user_for_config + @out.string.should_not match(Regexp.escape("Please enter the existing admin clientname: [chef-webui]")) + @knife.admin_client_name.should_not == 'chef-webui' + end + + it "asks the user for the location of the existing admin key if -i is specified" do + @knife.config[:initial] = true + @knife.ask_user_for_config + @out.string.should match(Regexp.escape("Please enter the location of the existing admin client's private key: [/etc/chef/webui.pem]")) + if windows? + @knife.admin_client_key.should == 'C:/etc/chef/webui.pem' + else + @knife.admin_client_key.should == '/etc/chef/webui.pem' + end + end + + it "should not ask the user for the location of the existing admin key if -i and --admin_client_key are specified" do + @knife.config[:initial] = true + @knife.config[:admin_client_key] = '/home/you/.chef/my-webui.pem' + @knife.ask_user_for_config + @out.string.should_not match(Regexp.escape("Please enter the location of the existing admin client's private key:")) + if windows? + @knife.admin_client_key.should == 'C:/home/you/.chef/my-webui.pem' + else + @knife.admin_client_key.should == '/home/you/.chef/my-webui.pem' + end + end + + it "should not ask the user for the location of the existing admin key if -i is not specified" do + @knife.ask_user_for_config + @out.string.should_not match(Regexp.escape("Please enter the location of the existing admin client's private key: [/etc/chef/webui.pem]")) + if windows? + @knife.admin_client_key.should_not == 'C:/etc//chef/webui.pem' + else + @knife.admin_client_key.should_not == '/etc/chef/webui.pem' + end + end + + it "asks the user for the location of a chef repo" do + @knife.ask_user_for_config + @out.string.should match(Regexp.escape("Please enter the path to a chef repository (or leave blank):")) + @knife.chef_repo.should == '' + end + + it "asks the users for the name of the validation client" do + @knife.ask_user_for_config + @out.string.should match(Regexp.escape("Please enter the validation clientname: [chef-validator]")) + @knife.validation_client_name.should == 'chef-validator' + end + + it "should not ask the users for the name of the validation client if --validation_client_name is specified" do + @knife.config[:validation_client_name] = 'my-validator' + @knife.ask_user_for_config + @out.string.should_not match(Regexp.escape("Please enter the validation clientname:")) + @knife.validation_client_name.should == 'my-validator' + end + + it "asks the users for the location of the validation key" do + @knife.ask_user_for_config + @out.string.should match(Regexp.escape("Please enter the location of the validation key: [/etc/chef/validation.pem]")) + if windows? + @knife.validation_key.should == 'C:/etc/chef/validation.pem' + else + @knife.validation_key.should == '/etc/chef/validation.pem' + end + end + + it "should not ask the users for the location of the validation key if --validation_key is specified" do + @knife.config[:validation_key] = '/home/you/.chef/my-validation.pem' + @knife.ask_user_for_config + @out.string.should_not match(Regexp.escape("Please enter the location of the validation key:")) + if windows? + @knife.validation_key.should == 'C:/home/you/.chef/my-validation.pem' + else + @knife.validation_key.should == '/home/you/.chef/my-validation.pem' + end + end + + it "should not ask the user for anything if -i and all other properties are specified" do + @knife.config[:initial] = true + @knife.config[:chef_server_url] = 'http://localhost:5000' + @knife.config[:node_name] = 'testnode' + @knife.config[:admin_client_name] = 'my-webui' + @knife.config[:admin_client_key] = '/home/you/.chef/my-webui.pem' + @knife.config[:validation_client_name] = 'my-validator' + @knife.config[:validation_key] = '/home/you/.chef/my-validation.pem' + @knife.config[:repository] = '' + @knife.config[:client_key] = '/home/you/a-new-user.pem' + Etc.stub!(:getlogin).and_return('a-new-user') + + @knife.ask_user_for_config + @out.string.should match(/\s*/) + + @knife.new_client_name.should == 'testnode' + @knife.chef_server.should == 'http://localhost:5000' + @knife.admin_client_name.should == 'my-webui' + if windows? + @knife.admin_client_key.should == 'C:/home/you/.chef/my-webui.pem' + @knife.validation_key.should == 'C:/home/you/.chef/my-validation.pem' + @knife.new_client_key.should == 'C:/home/you/a-new-user.pem' + else + @knife.admin_client_key.should == '/home/you/.chef/my-webui.pem' + @knife.validation_key.should == '/home/you/.chef/my-validation.pem' + @knife.new_client_key.should == '/home/you/a-new-user.pem' + end + @knife.validation_client_name.should == 'my-validator' + @knife.chef_repo.should == '' + end + + it "writes the new data to a config file" do + File.stub!(:expand_path).with("/home/you/.chef/knife.rb").and_return("/home/you/.chef/knife.rb") + File.stub!(:expand_path).with("/home/you/.chef/#{Etc.getlogin}.pem").and_return("/home/you/.chef/#{Etc.getlogin}.pem") + File.stub!(:expand_path).with("/etc/chef/validation.pem").and_return("/etc/chef/validation.pem") + File.stub!(:expand_path).with("/etc/chef/webui.pem").and_return("/etc/chef/webui.pem") + FileUtils.should_receive(:mkdir_p).with("/home/you/.chef") + config_file = StringIO.new + ::File.should_receive(:open).with("/home/you/.chef/knife.rb", "w").and_yield config_file + @knife.config[:repository] = '/home/you/chef-repo' + @knife.run + config_file.string.should match(/^node_name[\s]+'#{Etc.getlogin}'$/) + config_file.string.should match(%r{^client_key[\s]+'/home/you/.chef/#{Etc.getlogin}.pem'$}) + config_file.string.should match(/^validation_client_name\s+'chef-validator'$/) + config_file.string.should match(%r{^validation_key\s+'/etc/chef/validation.pem'$}) + config_file.string.should match(%r{^chef_server_url\s+'http://foo.example.org:4000'$}) + config_file.string.should match(%r{cookbook_path\s+\[ '/home/you/chef-repo/cookbooks' \]}) + end + + it "creates a new client when given the --initial option" do + File.stub!(:expand_path).with("/home/you/.chef/knife.rb").and_return("/home/you/.chef/knife.rb") + File.stub!(:expand_path).with("/home/you/.chef/a-new-user.pem").and_return("/home/you/.chef/a-new-user.pem") + File.stub!(:expand_path).with("/etc/chef/validation.pem").and_return("/etc/chef/validation.pem") + File.stub!(:expand_path).with("/etc/chef/webui.pem").and_return("/etc/chef/webui.pem") + Chef::Config[:node_name] = "webmonkey.example.com" + client_command = Chef::Knife::ClientCreate.new + client_command.should_receive(:run) + + Etc.stub!(:getlogin).and_return("a-new-user") + + Chef::Knife::ClientCreate.stub!(:new).and_return(client_command) + FileUtils.should_receive(:mkdir_p).with("/home/you/.chef") + ::File.should_receive(:open).with("/home/you/.chef/knife.rb", "w") + @knife.config[:initial] = true + @knife.run + client_command.name_args.should == Array("a-new-user") + client_command.config[:admin].should be_true + client_command.config[:file].should == "/home/you/.chef/a-new-user.pem" + client_command.config[:yes].should be_true + client_command.config[:disable_editing].should be_true + end +end diff --git a/spec/unit/knife/cookbook_bulk_delete_spec.rb b/spec/unit/knife/cookbook_bulk_delete_spec.rb new file mode 100644 index 0000000000..ced2a9a4e4 --- /dev/null +++ b/spec/unit/knife/cookbook_bulk_delete_spec.rb @@ -0,0 +1,87 @@ +# +# Author:: Stephen Delano (<stephen@opscode.com>) +# Copyright:: Copyright (c) 2010 Opscode, 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::CookbookBulkDelete do + before(:each) do + Chef::Log.logger = Logger.new(StringIO.new) + + Chef::Config[:node_name] = "webmonkey.example.com" + @knife = Chef::Knife::CookbookBulkDelete.new + @knife.config = {:print_after => nil} + @knife.name_args = ["."] + @stdout = StringIO.new + @knife.ui.stub!(:stdout).and_return(@stdout) + @knife.ui.stub!(:confirm).and_return(true) + @cookbooks = Hash.new + %w{cheezburger pizza lasagna}.each do |cookbook_name| + cookbook = Chef::CookbookVersion.new(cookbook_name) + @cookbooks[cookbook_name] = cookbook + end + @rest = mock("Chef::REST") + @rest.stub!(:get_rest).and_return(@cookbooks) + @rest.stub!(:delete_rest).and_return(true) + @knife.stub!(:rest).and_return(@rest) + Chef::CookbookVersion.stub!(:list).and_return(@cookbooks) + + end + + + + describe "when there are several cookbooks on the server" do + before do + @cheezburger = {'cheezburger' => {"url" => "file:///dev/null", "versions" => [{"url" => "file:///dev/null-cheez", "version" => "1.0.0"}]}} + @rest.stub!(:get_rest).with('cookbooks/cheezburger').and_return(@cheezburger) + @pizza = {'pizza' => {"url" => "file:///dev/null", "versions" => [{"url" => "file:///dev/null-pizza", "version" => "2.0.0"}]}} + @rest.stub!(:get_rest).with('cookbooks/pizza').and_return(@pizza) + @lasagna = {'lasagna' => {"url" => "file:///dev/null", "versions" => [{"url" => "file:///dev/null-lasagna", "version" => "3.0.0"}]}} + @rest.stub!(:get_rest).with('cookbooks/lasagna').and_return(@lasagna) + end + + it "should print the cookbooks you are about to delete" do + expected = @knife.ui.list(@cookbooks.keys.sort, :columns_down) + @knife.run + @stdout.string.should match(/#{expected}/) + end + + it "should confirm you really want to delete them" do + @knife.ui.should_receive(:confirm) + @knife.run + end + + it "should delete each cookbook" do + {"cheezburger" => "1.0.0", "pizza" => "2.0.0", "lasagna" => '3.0.0'}.each do |cookbook_name, version| + @rest.should_receive(:delete_rest).with("cookbooks/#{cookbook_name}/#{version}") + end + @knife.run + end + + it "should only delete cookbooks that match the regex" do + @knife.name_args = ["cheezburger"] + @rest.should_receive(:delete_rest).with('cookbooks/cheezburger/1.0.0') + @knife.run + end + end + + it "should exit if the regex is not provided" do + @knife.name_args = [] + lambda { @knife.run }.should raise_error(SystemExit) + end + +end diff --git a/spec/unit/knife/cookbook_create_spec.rb b/spec/unit/knife/cookbook_create_spec.rb new file mode 100644 index 0000000000..dc1d7d7f79 --- /dev/null +++ b/spec/unit/knife/cookbook_create_spec.rb @@ -0,0 +1,271 @@ +# +# Author:: Nuo Yan (<nuo@opscode.com>) +# Copyright:: Copyright (c) 2010 Opscode, 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' +require 'tmpdir' + +describe Chef::Knife::CookbookCreate do + before(:each) do + Chef::Config[:node_name] = "webmonkey.example.com" + @knife = Chef::Knife::CookbookCreate.new + @knife.config = {} + @knife.name_args = ["foobar"] + @stdout = StringIO.new + @knife.stub!(:stdout).and_return(@stdout) + end + + describe "run" do + + # Fixes CHEF-2579 + it "should expand the path of the cookbook directory" do + File.should_receive(:expand_path).with("~/tmp/monkeypants") + @knife.config = {:cookbook_path => "~/tmp/monkeypants"} + @knife.stub!(:create_cookbook) + @knife.stub!(:create_readme) + @knife.stub!(:create_changelog) + @knife.stub!(:create_metadata) + @knife.run + end + + it "should create a new cookbook with default values to copyright name, email, readme format and license if those are not supplied" do + @dir = Dir.tmpdir + @knife.config = {:cookbook_path => @dir} + @knife.should_receive(:create_cookbook).with(@dir, @knife.name_args.first, "YOUR_COMPANY_NAME", "none") + @knife.should_receive(:create_readme).with(@dir, @knife.name_args.first, "md") + @knife.should_receive(:create_changelog).with(@dir, @knife.name_args.first) + @knife.should_receive(:create_metadata).with(@dir, @knife.name_args.first, "YOUR_COMPANY_NAME", "YOUR_EMAIL", "none", "md") + @knife.run + end + + it "should create a new cookbook with specified company name in the copyright section if one is specified" do + @dir = Dir.tmpdir + @knife.config = { + :cookbook_path => @dir, + :cookbook_copyright => "Opscode, Inc" + } + @knife.name_args=["foobar"] + @knife.should_receive(:create_cookbook).with(@dir, @knife.name_args.first, "Opscode, Inc", "none") + @knife.should_receive(:create_readme).with(@dir, @knife.name_args.first, "md") + @knife.should_receive(:create_changelog).with(@dir, @knife.name_args.first) + @knife.should_receive(:create_metadata).with(@dir, @knife.name_args.first, "Opscode, Inc", "YOUR_EMAIL", "none", "md") + @knife.run + end + + it "should create a new cookbook with specified copyright name and email if they are specified" do + @dir = Dir.tmpdir + @knife.config = { + :cookbook_path => @dir, + :cookbook_copyright => "Opscode, Inc", + :cookbook_email => "nuo@opscode.com" + } + @knife.name_args=["foobar"] + @knife.should_receive(:create_cookbook).with(@dir, @knife.name_args.first, "Opscode, Inc", "none") + @knife.should_receive(:create_readme).with(@dir, @knife.name_args.first, "md") + @knife.should_receive(:create_changelog).with(@dir, @knife.name_args.first) + @knife.should_receive(:create_metadata).with(@dir, @knife.name_args.first, "Opscode, Inc", "nuo@opscode.com", "none", "md") + @knife.run + end + + it "should create a new cookbook with specified copyright name and email and license information (true) if they are specified" do + @dir = Dir.tmpdir + @knife.config = { + :cookbook_path => @dir, + :cookbook_copyright => "Opscode, Inc", + :cookbook_email => "nuo@opscode.com", + :cookbook_license => "apachev2" + } + @knife.name_args=["foobar"] + @knife.should_receive(:create_cookbook).with(@dir, @knife.name_args.first, "Opscode, Inc", "apachev2") + @knife.should_receive(:create_readme).with(@dir, @knife.name_args.first, "md") + @knife.should_receive(:create_changelog).with(@dir, @knife.name_args.first) + @knife.should_receive(:create_metadata).with(@dir, @knife.name_args.first, "Opscode, Inc", "nuo@opscode.com", "apachev2", "md") + @knife.run + end + + it "should create a new cookbook with specified copyright name and email and license information (false) if they are specified" do + @dir = Dir.tmpdir + @knife.config = { + :cookbook_path => @dir, + :cookbook_copyright => "Opscode, Inc", + :cookbook_email => "nuo@opscode.com", + :cookbook_license => false + } + @knife.name_args=["foobar"] + @knife.should_receive(:create_cookbook).with(@dir, @knife.name_args.first, "Opscode, Inc", "none") + @knife.should_receive(:create_readme).with(@dir, @knife.name_args.first, "md") + @knife.should_receive(:create_changelog).with(@dir, @knife.name_args.first) + @knife.should_receive(:create_metadata).with(@dir, @knife.name_args.first, "Opscode, Inc", "nuo@opscode.com", "none", "md") + @knife.run + end + + it "should create a new cookbook with specified copyright name and email and license information ('false' as string) if they are specified" do + @dir = Dir.tmpdir + @knife.config = { + :cookbook_path => @dir, + :cookbook_copyright => "Opscode, Inc", + :cookbook_email => "nuo@opscode.com", + :cookbook_license => "false" + } + @knife.name_args=["foobar"] + @knife.should_receive(:create_cookbook).with(@dir, @knife.name_args.first, "Opscode, Inc", "none") + @knife.should_receive(:create_readme).with(@dir, @knife.name_args.first, "md") + @knife.should_receive(:create_changelog).with(@dir, @knife.name_args.first) + @knife.should_receive(:create_metadata).with(@dir, @knife.name_args.first, "Opscode, Inc", "nuo@opscode.com", "none", "md") + @knife.run + end + + it "should allow specifying a gpl2 license" do + @dir = Dir.tmpdir + @knife.config = { + :cookbook_path => @dir, + :cookbook_copyright => "Opscode, Inc", + :cookbook_email => "nuo@opscode.com", + :cookbook_license => "gplv2" + } + @knife.name_args=["foobar"] + @knife.should_receive(:create_cookbook).with(@dir, @knife.name_args.first, "Opscode, Inc", "gplv2") + @knife.should_receive(:create_readme).with(@dir, @knife.name_args.first, "md") + @knife.should_receive(:create_changelog).with(@dir, @knife.name_args.first) + @knife.should_receive(:create_metadata).with(@dir, @knife.name_args.first, "Opscode, Inc", "nuo@opscode.com", "gplv2", "md") + @knife.run + end + + it "should allow specifying a gplv3 license" do + @dir = Dir.tmpdir + @knife.config = { + :cookbook_path => @dir, + :cookbook_copyright => "Opscode, Inc", + :cookbook_email => "nuo@opscode.com", + :cookbook_license => "gplv3" + } + @knife.name_args=["foobar"] + @knife.should_receive(:create_cookbook).with(@dir, @knife.name_args.first, "Opscode, Inc", "gplv3") + @knife.should_receive(:create_readme).with(@dir, @knife.name_args.first, "md") + @knife.should_receive(:create_changelog).with(@dir, @knife.name_args.first) + @knife.should_receive(:create_metadata).with(@dir, @knife.name_args.first, "Opscode, Inc", "nuo@opscode.com", "gplv3", "md") + @knife.run + end + + it "should allow specifying the mit license" do + @dir = Dir.tmpdir + @knife.config = { + :cookbook_path => @dir, + :cookbook_copyright => "Opscode, Inc", + :cookbook_email => "nuo@opscode.com", + :cookbook_license => "mit" + } + @knife.name_args=["foobar"] + @knife.should_receive(:create_cookbook).with(@dir, @knife.name_args.first, "Opscode, Inc", "mit") + @knife.should_receive(:create_readme).with(@dir, @knife.name_args.first, "md") + @knife.should_receive(:create_changelog).with(@dir, @knife.name_args.first) + @knife.should_receive(:create_metadata).with(@dir, @knife.name_args.first, "Opscode, Inc", "nuo@opscode.com", "mit", "md") + @knife.run + end + + it "should allow specifying the rdoc readme format" do + @dir = Dir.tmpdir + @knife.config = { + :cookbook_path => @dir, + :cookbook_copyright => "Opscode, Inc", + :cookbook_email => "nuo@opscode.com", + :cookbook_license => "mit", + :readme_format => "rdoc" + } + @knife.name_args=["foobar"] + @knife.should_receive(:create_cookbook).with(@dir, @knife.name_args.first, "Opscode, Inc", "mit") + @knife.should_receive(:create_readme).with(@dir, @knife.name_args.first, "rdoc") + @knife.should_receive(:create_changelog).with(@dir, @knife.name_args.first) + @knife.should_receive(:create_metadata).with(@dir, @knife.name_args.first, "Opscode, Inc", "nuo@opscode.com", "mit", "rdoc") + @knife.run + end + + it "should allow specifying the mkd readme format" do + @dir = Dir.tmpdir + @knife.config = { + :cookbook_path => @dir, + :cookbook_copyright => "Opscode, Inc", + :cookbook_email => "nuo@opscode.com", + :cookbook_license => "mit", + :readme_format => "mkd" + } + @knife.name_args=["foobar"] + @knife.should_receive(:create_cookbook).with(@dir, @knife.name_args.first, "Opscode, Inc", "mit") + @knife.should_receive(:create_readme).with(@dir, @knife.name_args.first, "mkd") + @knife.should_receive(:create_changelog).with(@dir, @knife.name_args.first) + @knife.should_receive(:create_metadata).with(@dir, @knife.name_args.first, "Opscode, Inc", "nuo@opscode.com", "mit", "mkd") + @knife.run + end + + it "should allow specifying the txt readme format" do + @dir = Dir.tmpdir + @knife.config = { + :cookbook_path => @dir, + :cookbook_copyright => "Opscode, Inc", + :cookbook_email => "nuo@opscode.com", + :cookbook_license => "mit", + :readme_format => "txt" + } + @knife.name_args=["foobar"] + @knife.should_receive(:create_cookbook).with(@dir, @knife.name_args.first, "Opscode, Inc", "mit") + @knife.should_receive(:create_readme).with(@dir, @knife.name_args.first, "txt") + @knife.should_receive(:create_changelog).with(@dir, @knife.name_args.first) + @knife.should_receive(:create_metadata).with(@dir, @knife.name_args.first, "Opscode, Inc", "nuo@opscode.com", "mit", "txt") + @knife.run + end + + it "should allow specifying an arbitrary readme format" do + @dir = Dir.tmpdir + @knife.config = { + :cookbook_path => @dir, + :cookbook_copyright => "Opscode, Inc", + :cookbook_email => "nuo@opscode.com", + :cookbook_license => "mit", + :readme_format => "foo" + } + @knife.name_args=["foobar"] + @knife.should_receive(:create_cookbook).with(@dir, @knife.name_args.first, "Opscode, Inc", "mit") + @knife.should_receive(:create_readme).with(@dir, @knife.name_args.first, "foo") + @knife.should_receive(:create_changelog).with(@dir, @knife.name_args.first) + @knife.should_receive(:create_metadata).with(@dir, @knife.name_args.first, "Opscode, Inc", "nuo@opscode.com", "mit", "foo") + @knife.run + end + + it "should create a CHANGELOG file" do + @dir = Dir.tmpdir + @knife.should_receive(:create_changelog).with(@dir, @knife.name_args.first) + @knife.run + end + + context "when the cookbooks path is not specified in the config file nor supplied via parameter" do + before do + @old_cookbook_path = Chef::Config[:cookbook_path] + Chef::Config[:cookbook_path] = nil + end + + it "should throw an argument error" do + @dir = Dir.tmpdir + lambda{@knife.run}.should raise_error(ArgumentError) + end + + after do + Chef::Config[:cookbook_path] = @old_cookbook_path + end + end + + end +end diff --git a/spec/unit/knife/cookbook_delete_spec.rb b/spec/unit/knife/cookbook_delete_spec.rb new file mode 100644 index 0000000000..afaa3b69a5 --- /dev/null +++ b/spec/unit/knife/cookbook_delete_spec.rb @@ -0,0 +1,239 @@ +# +# Author:: Thomas Bishop (<bishop.thomas@gmail.com>) +# Copyright:: Copyright (c) 2011 Thomas Bishop +# 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::CookbookDelete do + before(:each) do + @knife = Chef::Knife::CookbookDelete.new + @knife.name_args = ['foobar'] + @knife.cookbook_name = 'foobar' + @stdout = StringIO.new + @knife.ui.stub!(:stdout).and_return(@stdout) + @stderr = StringIO.new + @knife.ui.stub!(:stderr).and_return(@stderr) + end + + describe 'run' do + it 'should print usage and exit when a cookbook name is not provided' do + @knife.name_args = [] + @knife.should_receive(:show_usage) + @knife.ui.should_receive(:fatal) + lambda { @knife.run }.should raise_error(SystemExit) + end + + describe 'when specifying a cookbook name' do + it 'should delete the cookbook without a specific version' do + @knife.should_receive(:delete_without_explicit_version) + @knife.run + end + + describe 'and a version' do + it 'should delete the specific version of the cookbook' do + @knife.name_args << '1.0.0' + @knife.should_receive(:delete_explicit_version) + @knife.run + end + end + + describe 'with -a or --all' do + it 'should delete all versions of the cookbook' do + @knife.config[:all] = true + @knife.should_receive(:delete_all_versions) + @knife.run + end + end + + describe 'with -p or --purge' do + it 'should prompt to purge the files' do + @knife.config[:purge] = true + @knife.should_receive(:confirm). + with(/.+Are you sure you want to purge files.+/) + @knife.should_receive(:delete_without_explicit_version) + @knife.run + end + end + end + end + + describe 'delete_explicit_version' do + it 'should delete the specific cookbook version' do + @knife.cookbook_name = 'foobar' + @knife.version = '1.0.0' + @knife.should_receive(:delete_object).with(Chef::CookbookVersion, + 'foobar version 1.0.0', + 'cookbook').and_yield() + @knife.should_receive(:delete_request).with('cookbooks/foobar/1.0.0') + @knife.delete_explicit_version + end + end + + describe 'delete_all_versions' do + it 'should prompt to delete all versions of the cookbook' do + @knife.cookbook_name = 'foobar' + @knife.should_receive(:confirm).with('Do you really want to delete all versions of foobar') + @knife.should_receive(:delete_all_without_confirmation) + @knife.delete_all_versions + end + end + + describe 'delete_all_without_confirmation' do + it 'should delete all versions without confirmation' do + versions = ['1.0.0', '1.1.0'] + @knife.should_receive(:available_versions).and_return(versions) + versions.each do |v| + @knife.should_receive(:delete_version_without_confirmation).with(v) + end + @knife.delete_all_without_confirmation + end + end + + describe 'delete_without_explicit_version' do + it 'should exit if there are no available versions' do + @knife.should_receive(:available_versions).and_return(nil) + lambda { @knife.delete_without_explicit_version }.should raise_error(SystemExit) + end + + it 'should delete the version if only one is found' do + @knife.should_receive(:available_versions).at_least(:once).and_return(['1.0.0']) + @knife.should_receive(:delete_explicit_version) + @knife.delete_without_explicit_version + end + + it 'should ask which version(s) to delete if multiple are found' do + @knife.should_receive(:available_versions).at_least(:once).and_return(['1.0.0', '1.1.0']) + @knife.should_receive(:ask_which_versions_to_delete).and_return(['1.0.0', '1.1.0']) + @knife.should_receive(:delete_versions_without_confirmation).with(['1.0.0', '1.1.0']) + @knife.delete_without_explicit_version + end + end + + describe 'available_versions' do + before(:each) do + @rest_mock = mock('rest') + @knife.should_receive(:rest).and_return(@rest_mock) + @cookbook_data = { 'foobar' => { 'versions' => [{'version' => '1.0.0'}, + {'version' => '1.1.0'}, + {'version' => '2.0.0'} ]} + } + end + + it 'should return the list of versions of the cookbook' do + @rest_mock.should_receive(:get_rest).with('cookbooks/foobar').and_return(@cookbook_data) + @knife.available_versions.should == ['1.0.0', '1.1.0', '2.0.0'] + end + + it 'should raise if an error other than HTTP 404 is returned' do + exception = Net::HTTPServerException.new('500 Internal Server Error', '500') + @rest_mock.should_receive(:get_rest).and_raise(exception) + lambda { @knife.available_versions }.should raise_error Net::HTTPServerException + end + + describe "if the cookbook can't be found" do + before(:each) do + @rest_mock.should_receive(:get_rest). + and_raise(Net::HTTPServerException.new('404 Not Found', '404')) + end + + it 'should print an error' do + @knife.available_versions + @stderr.string.should match /error.+cannot find a cookbook named foobar/i + end + + it 'should return nil' do + @knife.available_versions.should == nil + end + end + end + + describe 'ask_which_version_to_delete' do + before(:each) do + @knife.stub!(:available_versions).and_return(['1.0.0', '1.1.0', '2.0.0']) + end + + it 'should prompt the user to select a version' do + prompt = /Which version\(s\) do you want to delete\?.+1\. foobar 1\.0\.0.+2\. foobar 1\.1\.0.+3\. foobar 2\.0\.0.+4\. All versions.+/m + @knife.should_receive(:ask_question).with(prompt).and_return('1') + @knife.ask_which_versions_to_delete + end + + it "should print an error and exit if a version wasn't specified" do + @knife.should_receive(:ask_question).and_return('') + @knife.ui.should_receive(:error).with(/no versions specified/i) + lambda { @knife.ask_which_versions_to_delete }.should raise_error(SystemExit) + end + + it 'should print an error if an invalid choice was selected' do + @knife.should_receive(:ask_question).and_return('100') + @knife.ui.should_receive(:error).with(/100 is not a valid choice/i) + @knife.ask_which_versions_to_delete + end + + it 'should return the selected versions' do + @knife.should_receive(:ask_question).and_return('1, 3') + @knife.ask_which_versions_to_delete.should == ['1.0.0', '2.0.0'] + end + + it "should return all of the versions if 'all' was selected" do + @knife.should_receive(:ask_question).and_return('4') + @knife.ask_which_versions_to_delete.should == [:all] + end + end + + describe 'delete_version_without_confirmation' do + it 'should delete the cookbook version' do + @knife.should_receive(:delete_request).with('cookbooks/foobar/1.0.0') + @knife.delete_version_without_confirmation('1.0.0') + end + + it 'should output that the cookbook was deleted' do + @knife.stub!(:delete_request) + @knife.delete_version_without_confirmation('1.0.0') + @stdout.string.should match /deleted cookbook\[foobar\]\[1.0.0\]/im + end + + describe 'with --print-after' do + it 'should display the cookbook data' do + object = '' + @knife.config[:print_after] = true + @knife.stub!(:delete_request).and_return(object) + @knife.should_receive(:format_for_display).with(object) + @knife.delete_version_without_confirmation('1.0.0') + end + end + end + + describe 'delete_versions_without_confirmation' do + it 'should delete each version without confirmation' do + versions = ['1.0.0', '1.1.0'] + versions.each do |v| + @knife.should_receive(:delete_version_without_confirmation).with(v) + end + @knife.delete_versions_without_confirmation(versions) + end + + describe 'with -a or --all' do + it 'should delete all versions without confirmation' do + versions = [:all] + @knife.should_receive(:delete_all_without_confirmation) + @knife.delete_versions_without_confirmation(versions) + end + end + end + +end diff --git a/spec/unit/knife/cookbook_download_spec.rb b/spec/unit/knife/cookbook_download_spec.rb new file mode 100644 index 0000000000..6ae3fbecd2 --- /dev/null +++ b/spec/unit/knife/cookbook_download_spec.rb @@ -0,0 +1,217 @@ +# +# Author:: Thomas Bishop (<bishop.thomas@gmail.com>) +# Copyright:: Copyright (c) 2011 Thomas Bishop +# 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::CookbookDownload do + before(:each) do + @knife = Chef::Knife::CookbookDownload.new + @stdout = StringIO.new + @knife.ui.stub!(:stdout).and_return(@stdout) + end + + describe 'run' do + it 'should print usage and exit when a cookbook name is not provided' do + @knife.name_args = [] + @knife.should_receive(:show_usage) + @knife.ui.should_receive(:fatal).with(/must specify a cookbook name/) + lambda { @knife.run }.should raise_error(SystemExit) + end + + describe 'with a cookbook name' do + before(:each) do + @knife.name_args = ['foobar'] + @knife.config[:download_directory] = '/var/tmp/chef' + @rest_mock = mock('rest') + @knife.stub(:rest).and_return(@rest_mock) + + @manifest_data = { + :recipes => [ + {'path' => 'recipes/foo.rb', + 'url' => 'http://example.org/files/foo.rb'}, + {'path' => 'recipes/bar.rb', + 'url' => 'http://example.org/files/bar.rb'} + ], + :templates => [ + {'path' => 'templates/default/foo.erb', + 'url' => 'http://example.org/files/foo.erb'}, + {'path' => 'templates/default/bar.erb', + 'url' => 'http://example.org/files/bar.erb'} + ], + :attributes => [ + {'path' => 'attributes/default.rb', + 'url' => 'http://example.org/files/default.rb'} + ] + } + + @cookbook_mock = mock('cookbook') + @cookbook_mock.stub!(:version).and_return('1.0.0') + @cookbook_mock.stub!(:manifest).and_return(@manifest_data) + @rest_mock.should_receive(:get_rest).with('cookbooks/foobar/1.0.0'). + and_return(@cookbook_mock) + end + + it 'should determine which version if one was not explicitly specified'do + @cookbook_mock.stub!(:manifest).and_return({}) + @knife.should_receive(:determine_version).and_return('1.0.0') + File.should_receive(:exists?).with('/var/tmp/chef/foobar-1.0.0').and_return(false) + Chef::CookbookVersion.stub!(:COOKBOOK_SEGEMENTS).and_return([]) + @knife.run + end + + describe 'and a version' do + before(:each) do + @knife.name_args << '1.0.0' + @files = @manifest_data.values.map { |v| v.map { |i| i['path'] } }.flatten.uniq + @files_mocks = {} + @files.map { |f| File.basename(f) }.flatten.uniq.each do |f| + @files_mocks[f] = mock("#{f}_mock") + @files_mocks[f].stub!(:path).and_return("/var/tmp/#{f}") + end + end + + it 'should print an error and exit if the cookbook download directory already exists' do + File.should_receive(:exists?).with('/var/tmp/chef/foobar-1.0.0').and_return(true) + @knife.ui.should_receive(:fatal).with(/\/var\/tmp\/chef\/foobar-1\.0\.0 exists/i) + lambda { @knife.run }.should raise_error(SystemExit) + end + + describe 'when downloading the cookbook' do + before(:each) do + @files.map { |f| File.dirname(f) }.flatten.uniq.each do |dir| + FileUtils.should_receive(:mkdir_p).with("/var/tmp/chef/foobar-1.0.0/#{dir}"). + at_least(:once) + end + + @files_mocks.each_pair do |file, mock| + @rest_mock.should_receive(:get_rest).with("http://example.org/files/#{file}", true). + and_return(mock) + end + + @rest_mock.should_receive(:sign_on_redirect=).with(false).at_least(:once) + @files.each do |f| + FileUtils.should_receive(:mv). + with("/var/tmp/#{File.basename(f)}", "/var/tmp/chef/foobar-1.0.0/#{f}") + end + end + + it "should download the cookbook when the cookbook download directory doesn't exist" do + File.should_receive(:exists?).with('/var/tmp/chef/foobar-1.0.0').and_return(false) + @knife.run + ['attributes', 'recipes', 'templates'].each do |segment| + @stdout.string.should match /downloading #{segment}/im + end + @stdout.string.should match /downloading foobar cookbook version 1\.0\.0/im + @stdout.string.should match /cookbook downloaded to \/var\/tmp\/chef\/foobar-1\.0\.0/im + end + + describe 'with -f or --force' do + it 'should remove the existing the cookbook download directory if it exists' do + @knife.config[:force] = true + File.should_receive(:exists?).with('/var/tmp/chef/foobar-1.0.0').and_return(true) + FileUtils.should_receive(:rm_rf).with('/var/tmp/chef/foobar-1.0.0') + @knife.run + end + end + end + + end + end + + end + + describe 'determine_version' do + it 'should return and set the version if there is only one version' do + @knife.should_receive(:available_versions).at_least(:once).and_return(['1.0.0']) + @knife.determine_version.should == '1.0.0' + @knife.version.should == '1.0.0' + end + + it 'should ask which version to download and return it if there is more than one' do + @knife.should_receive(:available_versions).and_return(['1.0.0', '2.0.0']) + @knife.should_receive(:ask_which_version).and_return('1.0.0') + @knife.determine_version.should == '1.0.0' + end + + describe 'with -N or --latest' do + it 'should return and set the version to the latest version' do + @knife.config[:latest] = true + @knife.should_receive(:available_versions).at_least(:once). + and_return(['1.0.0', '2.0.0', '1.1.0']) + @knife.determine_version + @knife.version.to_s.should == '2.0.0' + end + end + end + + describe 'available_versions' do + before(:each) do + @knife.cookbook_name = 'foobar' + end + + it 'should return the available vesions' do + Chef::CookbookVersion.should_receive(:available_versions). + with('foobar'). + and_return(['1.1.0', '2.0.0', '1.0.0']) + @knife.available_versions.should == [Chef::Version.new('1.0.0'), + Chef::Version.new('1.1.0'), + Chef::Version.new('2.0.0')] + end + + it 'should avoid multiple API calls to the server' do + Chef::CookbookVersion.should_receive(:available_versions). + once. + with('foobar'). + and_return(['1.1.0', '2.0.0', '1.0.0']) + @knife.available_versions + @knife.available_versions + end + end + + describe 'ask_which_version' do + before(:each) do + @knife.cookbook_name = 'foobar' + @knife.stub!(:available_versions).and_return(['1.0.0', '1.1.0', '2.0.0']) + end + + it 'should prompt the user to select a version' do + prompt = /Which version do you want to download\?.+1\. foobar 1\.0\.0.+2\. foobar 1\.1\.0.+3\. foobar 2\.0\.0.+/m + @knife.should_receive(:ask_question).with(prompt).and_return('1') + @knife.ask_which_version + end + + it "should set the version to the user's selection" do + @knife.should_receive(:ask_question).and_return('1') + @knife.ask_which_version + @knife.version.should == '1.0.0' + end + + it "should print an error and exit if a version wasn't specified" do + @knife.should_receive(:ask_question).and_return('') + @knife.ui.should_receive(:error).with(/is not a valid value/i) + lambda { @knife.ask_which_version }.should raise_error(SystemExit) + end + + it 'should print an error if an invalid choice was selected' do + @knife.should_receive(:ask_question).and_return('100') + @knife.ui.should_receive(:error).with(/'100' is not a valid value/i) + lambda { @knife.ask_which_version }.should raise_error(SystemExit) + end + end + +end diff --git a/spec/unit/knife/cookbook_list_spec.rb b/spec/unit/knife/cookbook_list_spec.rb new file mode 100644 index 0000000000..db6f061bd1 --- /dev/null +++ b/spec/unit/knife/cookbook_list_spec.rb @@ -0,0 +1,88 @@ +# +# Author:: Thomas Bishop (<bishop.thomas@gmail.com>) +# Copyright:: Copyright (c) 2011 Thomas Bishop +# 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::CookbookList do + before do + @knife = Chef::Knife::CookbookList.new + @rest_mock = mock('rest') + @knife.stub!(:rest).and_return(@rest_mock) + @cookbook_names = ['apache2', 'mysql'] + @base_url = 'https://server.example.com/cookbooks' + @cookbook_data = {} + @cookbook_names.each do |item| + @cookbook_data[item] = {'url' => "#{@base_url}/#{item}", + 'versions' => [{'version' => '1.0.1', + 'url' => "#{@base_url}/#{item}/1.0.1"}]} + end + @stdout = StringIO.new + @knife.ui.stub!(:stdout).and_return(@stdout) + end + + describe 'run' do + it 'should display the latest version of the cookbooks' do + @rest_mock.should_receive(:get_rest).with('/cookbooks?num_versions=1'). + and_return(@cookbook_data) + @knife.run + @cookbook_names.each do |item| + @stdout.string.should match /#{item}\s+1\.0\.1/ + end + end + + it 'should query cookbooks for the configured environment' do + @knife.config[:environment] = 'production' + @rest_mock.should_receive(:get_rest). + with('/environments/production/cookbooks?num_versions=1'). + and_return(@cookbook_data) + @knife.run + end + + describe 'with -w or --with-uri' do + it 'should display the cookbook uris' do + @knife.config[:with_uri] = true + @rest_mock.stub(:get_rest).and_return(@cookbook_data) + @knife.run + @cookbook_names.each do |item| + pattern = /#{Regexp.escape(@cookbook_data[item]['versions'].first['url'])}/ + @stdout.string.should match pattern + end + end + end + + describe 'with -a or --all' do + before do + @cookbook_names.each do |item| + @cookbook_data[item]['versions'] << {'version' => '1.0.0', + 'url' => "#{@base_url}/#{item}/1.0.0"} + end + end + + it 'should display all versions of the cookbooks' do + @knife.config[:all_versions] = true + @rest_mock.should_receive(:get_rest).with('/cookbooks?num_versions=all'). + and_return(@cookbook_data) + @knife.run + @cookbook_names.each do |item| + @stdout.string.should match /#{item}\s+1\.0\.1\s+1\.0\.0/ + end + end + end + + end +end diff --git a/spec/unit/knife/cookbook_metadata_from_file_spec.rb b/spec/unit/knife/cookbook_metadata_from_file_spec.rb new file mode 100644 index 0000000000..60555d89dc --- /dev/null +++ b/spec/unit/knife/cookbook_metadata_from_file_spec.rb @@ -0,0 +1,65 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Author:: Matthew Kent (<mkent@magoazul.com>) +# Copyright:: Copyright (c) 2008 Opscode, Inc. +# Copyright:: Copyright (c) 2010 Opscode, 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::CookbookMetadataFromFile do + before(:each) do + Chef::Config[:node_name] = "webmonkey.example.com" + @src = File.expand_path(File.join(CHEF_SPEC_DATA, "metadata", "quick_start", "metadata.rb")) + @tgt = File.expand_path(File.join(CHEF_SPEC_DATA, "metadata", "quick_start", "metadata.json")) + @knife = Chef::Knife::CookbookMetadataFromFile.new + @knife.name_args = [ @src ] + @knife.stub!(:to_json_pretty).and_return(true) + @md = Chef::Cookbook::Metadata.new + Chef::Cookbook::Metadata.stub(:new).and_return(@md) + $stdout.stub!(:write) + end + + after do + if File.exists?(@tgt) + File.unlink(@tgt) + end + end + + describe "run" do + it "should determine cookbook name from path" do + @md.should_receive(:name).with() + @md.should_receive(:name).with("quick_start") + @knife.run + end + + it "should load the metadata source" do + @md.should_receive(:from_file).with(@src) + @knife.run + end + + it "should write out the metadata to the correct location" do + File.should_receive(:open).with(@tgt, "w") + @knife.run + end + + it "should generate json from the metadata" do + Chef::JSONCompat.should_receive(:to_json_pretty).with(@md) + @knife.run + end + + end +end diff --git a/spec/unit/knife/cookbook_metadata_spec.rb b/spec/unit/knife/cookbook_metadata_spec.rb new file mode 100644 index 0000000000..c664326a3d --- /dev/null +++ b/spec/unit/knife/cookbook_metadata_spec.rb @@ -0,0 +1,179 @@ +# +# Author:: Thomas Bishop (<bishop.thomas@gmail.com>) +# Copyright:: Copyright (c) 2011 Thomas Bishop +# 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::CookbookMetadata do + before(:each) do + @knife = Chef::Knife::CookbookMetadata.new + @knife.name_args = ['foobar'] + @cookbook_dir = Dir.mktmpdir + @json_data = '{ "version": "1.0.0" }' + @stdout = StringIO.new + @stderr = StringIO.new + @knife.ui.stub!(:stdout).and_return(@stdout) + @knife.ui.stub!(:stderr).and_return(@stderr) + end + + describe 'run' do + it 'should print an error and exit if a cookbook name was not provided' do + @knife.name_args = [] + @knife.ui.should_receive(:error).with(/you must specify the cookbook.+use the --all/i) + lambda { @knife.run }.should raise_error(SystemExit) + end + + it 'should print an error and exit if an empty cookbook name was provided' do + @knife.name_args = [''] + @knife.ui.should_receive(:error).with(/you must specify the cookbook.+use the --all/i) + lambda { @knife.run }.should raise_error(SystemExit) + end + + it 'should generate the metadata for the cookbook' do + @knife.should_receive(:generate_metadata).with('foobar') + @knife.run + end + + describe 'with -a or --all' do + before(:each) do + @knife.config[:all] = true + @foo = Chef::CookbookVersion.new('foo') + @foo.version = '1.0.0' + @bar = Chef::CookbookVersion.new('bar') + @bar.version = '2.0.0' + @cookbook_loader = { + "foo" => @foo, + "bar" => @bar + } + @cookbook_loader.should_receive(:load_cookbooks).and_return(@cookbook_loader) + @knife.should_receive(:generate_metadata).with('foo') + @knife.should_receive(:generate_metadata).with('bar') + end + + it 'should generate the metadata for each cookbook' do + Chef::Config[:cookbook_path] = @cookbook_dir + Chef::CookbookLoader.should_receive(:new).with(@cookbook_dir).and_return(@cookbook_loader) + @knife.run + end + + describe 'and with -o or --cookbook-path' do + it 'should look in the provided path and generate cookbook metadata' do + @knife.config[:cookbook_path] = '/opt/chef/cookbooks' + Chef::CookbookLoader.should_receive(:new).with('/opt/chef/cookbooks').and_return(@cookbook_loader) + @knife.run + end + end + end + + end + + describe 'generate_metadata' do + before(:each) do + @knife.config[:cookbook_path] = @cookbook_dir + File.stub!(:expand_path).with("#{@cookbook_dir}/foobar/metadata.rb"). + and_return("#{@cookbook_dir}/foobar/metadata.rb") + end + + it 'should generate the metadata from metadata.rb if it exists' do + File.should_receive(:exists?).with("#{@cookbook_dir}/foobar/metadata.rb"). + and_return(true) + @knife.should_receive(:generate_metadata_from_file).with('foobar', "#{@cookbook_dir}/foobar/metadata.rb") + @knife.run + end + + it 'should validate the metadata json if metadata.rb does not exist' do + File.should_receive(:exists?).with("#{@cookbook_dir}/foobar/metadata.rb"). + and_return(false) + @knife.should_receive(:validate_metadata_json).with(@cookbook_dir, 'foobar') + @knife.run + end + end + + describe 'generate_metadata_from_file' do + before(:each) do + @metadata_mock = mock('metadata') + @json_file_mock = mock('json_file') + end + + it 'should generate the metatdata json from metatdata.rb' do + Chef::Cookbook::Metadata.stub!(:new).and_return(@metadata_mock) + @metadata_mock.should_receive(:name).with('foobar') + @metadata_mock.should_receive(:from_file).with("#{@cookbook_dir}/foobar/metadata.rb") + File.should_receive(:open).with("#{@cookbook_dir}/foobar/metadata.json", 'w'). + and_yield(@json_file_mock) + @json_file_mock.should_receive(:write).with(@json_data) + Chef::JSONCompat.should_receive(:to_json_pretty).with(@metadata_mock). + and_return(@json_data) + @knife.generate_metadata_from_file('foobar', "#{@cookbook_dir}/foobar/metadata.rb") + @stdout.string.should match /generating metadata for foobar from #{@cookbook_dir}\/foobar\/metadata\.rb/im + end + + { Chef::Exceptions::ObsoleteDependencySyntax => 'obsolote dependency', + Chef::Exceptions::InvalidVersionConstraint => 'invalid version constraint' + }.each_pair do |klass, description| + it "should print an error and exit when an #{description} syntax exception is encountered" do + exception = klass.new("#{description} blah") + Chef::Cookbook::Metadata.stub!(:new).and_raise(exception) + lambda { + @knife.generate_metadata_from_file('foobar', "#{@cookbook_dir}/foobar/metadata.rb") + }.should raise_error(SystemExit) + @stderr.string.should match /error: the cookbook 'foobar' contains invalid or obsolete metadata syntax/im + @stderr.string.should match /in #{@cookbook_dir}\/foobar\/metadata\.rb/im + @stderr.string.should match /#{description} blah/im + end + end + end + + describe 'validate_metadata_json' do + it 'should validate the metadata json' do + File.should_receive(:exist?).with("#{@cookbook_dir}/foobar/metadata.json"). + and_return(true) + IO.should_receive(:read).with("#{@cookbook_dir}/foobar/metadata.json"). + and_return(@json_data) + Chef::Cookbook::Metadata.should_receive(:validate_json).with(@json_data) + @knife.validate_metadata_json(@cookbook_dir, 'foobar') + end + + it 'should not try to validate the metadata json if the file does not exist' do + File.should_receive(:exist?).with("#{@cookbook_dir}/foobar/metadata.json"). + and_return(false) + IO.should_not_receive(:read) + Chef::Cookbook::Metadata.should_not_receive(:validate_json) + @knife.validate_metadata_json(@cookbook_dir, 'foobar') + end + + { Chef::Exceptions::ObsoleteDependencySyntax => 'obsolote dependency', + Chef::Exceptions::InvalidVersionConstraint => 'invalid version constraint' + }.each_pair do |klass, description| + it "should print an error and exit when an #{description} syntax exception is encountered" do + File.should_receive(:exist?).with("#{@cookbook_dir}/foobar/metadata.json"). + and_return(true) + IO.should_receive(:read).with("#{@cookbook_dir}/foobar/metadata.json"). + and_return(@json_data) + exception = klass.new("#{description} blah") + Chef::Cookbook::Metadata.stub!(:validate_json).and_raise(exception) + lambda { + @knife.validate_metadata_json(@cookbook_dir, 'foobar') + }.should raise_error(SystemExit) + @stderr.string.should match /error: the cookbook 'foobar' contains invalid or obsolete metadata syntax/im + @stderr.string.should match /in #{@cookbook_dir}\/foobar\/metadata\.json/im + @stderr.string.should match /#{description} blah/im + end + end + end + +end diff --git a/spec/unit/knife/cookbook_show_spec.rb b/spec/unit/knife/cookbook_show_spec.rb new file mode 100644 index 0000000000..2f2d841fea --- /dev/null +++ b/spec/unit/knife/cookbook_show_spec.rb @@ -0,0 +1,223 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Copyright:: Copyright (c) 2008 Opscode, Inc. +# License:: Apache License, eersion 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. +# + +# rename to cookbook not coookbook +require 'spec_helper' + +describe Chef::Knife::CookbookShow do + before(:each) do + Chef::Config[:node_name] = "webmonkey.example.com" + @knife = Chef::Knife::CookbookShow.new + @knife.config = { } + @knife.name_args = [ "cookbook_name" ] + @rest = mock(Chef::REST) + @knife.stub!(:rest).and_return(@rest) + @knife.stub!(:pretty_print).and_return(true) + @knife.stub!(:output).and_return(true) + end + + describe "run" do + describe "with 0 arguments: help" do + it 'should should print usage and exit when given no arguments' do + @knife.name_args = [] + @knife.should_receive(:show_usage) + @knife.ui.should_receive(:fatal) + lambda { @knife.run }.should raise_error(SystemExit) + end + end + + describe "with 1 argument: versions" do + before(:each) do + @response = { + "cookbook_name" => { + "url" => "http://url/cookbooks/cookbook_name", + "versions" => [ + { "version" => "0.10.0", "url" => "http://url/cookbooks/cookbook_name/0.10.0" }, + { "version" => "0.9.0", "url" => "http://url/cookbookx/cookbook_name/0.9.0" }, + { "version" => "0.8.0", "url" => "http://url/cookbooks/cookbook_name/0.8.0" } + ] + } + } + end + + it "should show the raw cookbook data" do + @rest.should_receive(:get_rest).with("cookbooks/cookbook_name").and_return(@response) + @knife.should_receive(:format_cookbook_list_for_display).with(@response) + @knife.run + end + + it "should respect the user-supplied environment" do + @knife.config[:environment] = "foo" + @rest.should_receive(:get_rest).with("environments/foo/cookbooks/cookbook_name").and_return(@response) + @knife.should_receive(:format_cookbook_list_for_display).with(@response) + @knife.run + end + end + + describe "with 2 arguments: name and version" do + before(:each) do + @knife.name_args << "0.1.0" + @response = { "0.1.0" => { "recipes" => {"default.rb" => ""} } } + end + + it "should show the specific part of a cookbook" do + @rest.should_receive(:get_rest).with("cookbooks/cookbook_name/0.1.0").and_return(@response) + @knife.should_receive(:output).with(@response) + @knife.run + end + end + + describe "with 3 arguments: name, version, and segment" do + before(:each) do + @knife.name_args = [ "cookbook_name", "0.1.0", "recipes" ] + @cookbook_response = Chef::CookbookVersion.new("cookbook_name") + @manifest = { + "recipes" => [ + { + :name => "default.rb", + :path => "recipes/default.rb", + :checksum => "1234", + :url => "http://example.org/files/default.rb" + } + ] + } + @cookbook_response.manifest = @manifest + @response = {"name"=>"default.rb", "url"=>"http://example.org/files/default.rb", "checksum"=>"1234", "path"=>"recipes/default.rb"} + end + + it "should print the json of the part" do + @rest.should_receive(:get_rest).with("cookbooks/cookbook_name/0.1.0").and_return(@cookbook_response) + @knife.should_receive(:output).with(@cookbook_response.manifest["recipes"]) + @knife.run + end + end + + describe "with 4 arguments: name, version, segment and filename" do + before(:each) do + @knife.name_args = [ "cookbook_name", "0.1.0", "recipes", "default.rb" ] + @cookbook_response = Chef::CookbookVersion.new("cookbook_name") + @cookbook_response.manifest = { + "recipes" => [ + { + :name => "default.rb", + :path => "recipes/default.rb", + :checksum => "1234", + :url => "http://example.org/files/default.rb" + } + ] + } + @response = "Example recipe text" + end + + it "should print the raw result of the request (likely a file!)" do + @rest.should_receive(:get_rest).with("cookbooks/cookbook_name/0.1.0").and_return(@cookbook_response) + @rest.should_receive(:get_rest).with("http://example.org/files/default.rb", true).and_return(StringIO.new(@response)) + @knife.should_receive(:pretty_print).with(@response) + @knife.run + end + end + + describe "with 4 arguments: name, version, segment and filename -- with specificity" do + before(:each) do + @knife.name_args = [ "cookbook_name", "0.1.0", "files", "afile.rb" ] + @cookbook_response = Chef::CookbookVersion.new("cookbook_name") + @cookbook_response.manifest = { + "files" => [ + { + :name => "afile.rb", + :path => "files/host-examplehost.example.org/afile.rb", + :checksum => "1111", + :specificity => "host-examplehost.example.org", + :url => "http://example.org/files/1111" + }, + { + :name => "afile.rb", + :path => "files/ubuntu-9.10/afile.rb", + :checksum => "2222", + :specificity => "ubuntu-9.10", + :url => "http://example.org/files/2222" + }, + { + :name => "afile.rb", + :path => "files/ubuntu/afile.rb", + :checksum => "3333", + :specificity => "ubuntu", + :url => "http://example.org/files/3333" + }, + { + :name => "afile.rb", + :path => "files/default/afile.rb", + :checksum => "4444", + :specificity => "default", + :url => "http://example.org/files/4444" + }, + ] + } + + @response = "Example recipe text" + end + + describe "with --fqdn" do + it "should pass the fqdn" do + @knife.config[:platform] = "example_platform" + @knife.config[:platform_version] = "1.0" + @knife.config[:fqdn] = "examplehost.example.org" + @rest.should_receive(:get_rest).with("cookbooks/cookbook_name/0.1.0").and_return(@cookbook_response) + @rest.should_receive(:get_rest).with("http://example.org/files/1111", true).and_return(StringIO.new(@response)) + @knife.should_receive(:pretty_print).with(@response) + @knife.run + end + end + + describe "and --platform" do + it "should pass the platform" do + @knife.config[:platform] = "ubuntu" + @knife.config[:platform_version] = "1.0" + @knife.config[:fqdn] = "differenthost.example.org" + @rest.should_receive(:get_rest).with("cookbooks/cookbook_name/0.1.0").and_return(@cookbook_response) + @rest.should_receive(:get_rest).with("http://example.org/files/3333", true).and_return(StringIO.new(@response)) + @knife.should_receive(:pretty_print).with(@response) + @knife.run + end + end + + describe "and --platform-version" do + it "should pass the platform" do + @knife.config[:platform] = "ubuntu" + @knife.config[:platform_version] = "9.10" + @knife.config[:fqdn] = "differenthost.example.org" + @rest.should_receive(:get_rest).with("cookbooks/cookbook_name/0.1.0").and_return(@cookbook_response) + @rest.should_receive(:get_rest).with("http://example.org/files/2222", true).and_return(StringIO.new(@response)) + @knife.should_receive(:pretty_print).with(@response) + @knife.run + end + end + + describe "with none of the arguments, it should use the default" do + it "should pass them all" do + @rest.should_receive(:get_rest).with("cookbooks/cookbook_name/0.1.0").and_return(@cookbook_response) + @rest.should_receive(:get_rest).with("http://example.org/files/4444", true).and_return(StringIO.new(@response)) + @knife.should_receive(:pretty_print).with(@response) + @knife.run + end + end + + end + end +end + diff --git a/spec/unit/knife/cookbook_site_download_spec.rb b/spec/unit/knife/cookbook_site_download_spec.rb new file mode 100644 index 0000000000..a3d43c5b4a --- /dev/null +++ b/spec/unit/knife/cookbook_site_download_spec.rb @@ -0,0 +1,151 @@ +# +# Author:: Thomas Bishop (<bishop.thomas@gmail.com>) +# Copyright:: Copyright (c) 2012 Thomas Bishop +# 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 File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') + +describe Chef::Knife::CookbookSiteDownload do + + describe 'run' do + before do + @knife = Chef::Knife::CookbookSiteDownload.new + @knife.name_args = ['apache2'] + @noauth_rest = mock 'no auth rest' + @stdout = StringIO.new + @cookbook_api_url = 'http://cookbooks.opscode.com/api/v1/cookbooks' + @version = '1.0.2' + @version_us = @version.gsub '.', '_' + @current_data = { 'deprecated' => false, + 'latest_version' => "#{@cookbook_api_url}/apache2/versions/#{@version_us}", + 'replacement' => 'other_apache2' } + + @knife.ui.stub(:stdout).and_return(@stdout) + @knife.stub(:noauth_rest).and_return(@noauth_rest) + @noauth_rest.should_receive(:get_rest). + with("#{@cookbook_api_url}/apache2"). + and_return(@current_data) + end + + context 'when the cookbook is deprecated and not forced' do + before do + @current_data['deprecated'] = true + end + + it 'should warn with info about the replacement' do + @knife.ui.should_receive(:warn). + with(/.+deprecated.+replaced by other_apache2.+/i) + @knife.ui.should_receive(:warn). + with(/use --force.+download.+/i) + @knife.run + end + end + + context 'when' do + before do + @cookbook_data = { 'version' => @version, + 'file' => "http://example.com/apache2_#{@version_us}.tgz" } + @temp_file = stub :path => "/tmp/apache2_#{@version_us}.tgz" + @file = File.join(Dir.pwd, "apache2-#{@version}.tar.gz") + + @noauth_rest.should_receive(:sign_on_redirect=).with(false) + end + + context 'downloading the latest version' do + before do + @noauth_rest.should_receive(:get_rest). + with(@current_data['latest_version']). + and_return(@cookbook_data) + @noauth_rest.should_receive(:get_rest). + with(@cookbook_data['file'], true). + and_return(@temp_file) + end + + context 'and it is deprecated and with --force' do + before do + @current_data['deprecated'] = true + @knife.config[:force] = true + end + + it 'should download the latest version' do + @knife.ui.should_receive(:warn). + with(/.+deprecated.+replaced by other_apache2.+/i) + FileUtils.should_receive(:cp).with(@temp_file.path, @file) + @knife.run + @stdout.string.should match /downloading apache2.+version.+#{Regexp.escape(@version)}/i + @stdout.string.should match /cookbook save.+#{Regexp.escape(@file)}/i + end + + end + + it 'should download the latest version' do + FileUtils.should_receive(:cp).with(@temp_file.path, @file) + @knife.run + @stdout.string.should match /downloading apache2.+version.+#{Regexp.escape(@version)}/i + @stdout.string.should match /cookbook save.+#{Regexp.escape(@file)}/i + end + + context 'with -f or --file' do + before do + @file = '/opt/chef/cookbooks/apache2.tar.gz' + @knife.config[:file] = @file + FileUtils.should_receive(:cp).with(@temp_file.path, @file) + end + + it 'should download the cookbook to the desired file' do + @knife.run + @stdout.string.should match /downloading apache2.+version.+#{Regexp.escape(@version)}/i + @stdout.string.should match /cookbook save.+#{Regexp.escape(@file)}/i + end + end + + it 'should provide an accessor to the version' do + FileUtils.stub(:cp).and_return(true) + @knife.version.should == @version + @knife.run + end + end + + context 'downloading a cookbook of a specific version' do + before do + @version = '1.0.1' + @version_us = @version.gsub '.', '_' + @cookbook_data = { 'version' => @version, + 'file' => "http://example.com/apache2_#{@version_us}.tgz" } + @temp_file = stub :path => "/tmp/apache2_#{@version_us}.tgz" + @file = File.join(Dir.pwd, "apache2-#{@version}.tar.gz") + @knife.name_args << @version + end + + it 'should download the desired version' do + @noauth_rest.should_receive(:get_rest). + with("#{@cookbook_api_url}/apache2/versions/#{@version_us}"). + and_return(@cookbook_data) + @noauth_rest.should_receive(:get_rest). + with(@cookbook_data['file'], true). + and_return(@temp_file) + FileUtils.should_receive(:cp).with(@temp_file.path, @file) + @knife.run + @stdout.string.should match /downloading apache2.+version.+#{Regexp.escape(@version)}/i + @stdout.string.should match /cookbook save.+#{Regexp.escape(@file)}/i + end + end + + end + + end + +end diff --git a/spec/unit/knife/cookbook_site_install_spec.rb b/spec/unit/knife/cookbook_site_install_spec.rb new file mode 100644 index 0000000000..2ec87b8d16 --- /dev/null +++ b/spec/unit/knife/cookbook_site_install_spec.rb @@ -0,0 +1,138 @@ +# +# Author:: Steven Danna (<steve@opscode.com>) +# Copyright:: Copyright (c) 2011 Opscode, 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper")) + +describe Chef::Knife::CookbookSiteInstall do + before(:each) do + require 'chef/knife/core/cookbook_scm_repo' + @knife = Chef::Knife::CookbookSiteInstall.new + @knife.config = {} + if Chef::Platform.windows? + @install_path = 'C:/tmp/chef' + else + @install_path = '/var/tmp/chef' + end + @knife.config[:cookbook_path] = [ @install_path ] + + @stdout = StringIO.new + @stderr = StringIO.new + @knife.stub!(:stderr).and_return(@stdout) + @knife.stub!(:stdout).and_return(@stdout) + + #Assume all external commands would have succeed. :( + File.stub!(:unlink) + File.stub!(:rmtree) + @knife.stub!(:shell_out!).and_return(true) + + #CookbookSiteDownload Stup + @downloader = {} + @knife.stub!(:download_cookbook_to).and_return(@downloader) + @downloader.stub!(:version).and_return do + if @knife.name_args.size == 2 + @knife.name_args[1] + else + "0.3.0" + end + end + + #Stubs for CookbookSCMRepo + @repo = stub(:sanity_check => true, :reset_to_default_state => true, + :prepare_to_import => true, :finalize_updates_to => true, + :merge_updates_from => true) + Chef::Knife::CookbookSCMRepo.stub!(:new).and_return(@repo) + end + + + describe "run" do + + it "should return an error if a cookbook name is not provided" do + @knife.name_args = [] + @knife.ui.should_receive(:error).with("Please specify a cookbook to download and install.") + lambda { @knife.run }.should raise_error(SystemExit) + end + + it "should return an error if more than two arguments are given" do + @knife.name_args = ["foo", "bar", "baz"] + @knife.ui.should_receive(:error).with("Installing multiple cookbooks at once is not supported.") + lambda { @knife.run }.should raise_error(SystemExit) + end + + it "should return an error if the second argument is not a version" do + @knife.name_args = ["getting-started", "1pass"] + @knife.ui.should_receive(:error).with("Installing multiple cookbooks at once is not supported.") + lambda { @knife.run }.should raise_error(SystemExit) + end + + it "should return an error if the second argument is a four-digit version" do + @knife.name_args = ["getting-started", "0.0.0.1"] + @knife.ui.should_receive(:error).with("Installing multiple cookbooks at once is not supported.") + lambda { @knife.run }.should raise_error(SystemExit) + end + + it "should return an error if the second argument is a one-digit version" do + @knife.name_args = ["getting-started", "1"] + @knife.ui.should_receive(:error).with("Installing multiple cookbooks at once is not supported.") + lambda { @knife.run }.should raise_error(SystemExit) + end + + + it "should install the specified version if second argument is a three-digit version" do + @knife.name_args = ["getting-started", "0.1.0"] + @knife.config[:no_deps] = true + upstream_file = File.join(@install_path, "getting-started.tar.gz") + @knife.should_receive(:download_cookbook_to).with(upstream_file) + @knife.should_receive(:extract_cookbook).with(upstream_file, "0.1.0") + @knife.should_receive(:clear_existing_files).with(File.join(@install_path, "getting-started")) + @repo.should_receive(:merge_updates_from).with("getting-started", "0.1.0") + @knife.run + end + + it "should install the specified version if second argument is a two-digit version" do + @knife.name_args = ["getting-started", "0.1"] + @knife.config[:no_deps] = true + upstream_file = File.join(@install_path, "getting-started.tar.gz") + @knife.should_receive(:download_cookbook_to).with(upstream_file) + @knife.should_receive(:extract_cookbook).with(upstream_file, "0.1") + @knife.should_receive(:clear_existing_files).with(File.join(@install_path, "getting-started")) + @repo.should_receive(:merge_updates_from).with("getting-started", "0.1") + @knife.run + end + + it "should install the latest version if only a cookbook name is given" do + @knife.name_args = ["getting-started"] + @knife.config[:no_deps] = true + upstream_file = File.join(@install_path, "getting-started.tar.gz") + @knife.should_receive(:download_cookbook_to).with(upstream_file) + @knife.should_receive(:extract_cookbook).with(upstream_file, "0.3.0") + @knife.should_receive(:clear_existing_files).with(File.join(@install_path, "getting-started")) + @repo.should_receive(:merge_updates_from).with("getting-started", "0.3.0") + @knife.run + end + + it "should not create/reset git branches if use_current_branch is set" do + @knife.name_args = ["getting-started"] + @knife.config[:use_current_branch] = true + @knife.config[:no_deps] = true + upstream_file = File.join(@install_path, "getting-started.tar.gz") + @repo.should_not_receive(:prepare_to_import) + @repo.should_not_receive(:reset_to_default_state) + @knife.run + end + end +end diff --git a/spec/unit/knife/cookbook_site_share_spec.rb b/spec/unit/knife/cookbook_site_share_spec.rb new file mode 100644 index 0000000000..3b912af0c5 --- /dev/null +++ b/spec/unit/knife/cookbook_site_share_spec.rb @@ -0,0 +1,146 @@ +# +# Author:: Stephen Delano (<stephen@opscode.com>) +# Copyright:: Copyright (c) 2010 Opscode, 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' + +require 'chef/cookbook_uploader' +require 'chef/cookbook_site_streaming_uploader' + +describe Chef::Knife::CookbookSiteShare do + + before(:each) do + @knife = Chef::Knife::CookbookSiteShare.new + @knife.name_args = ['cookbook_name', 'AwesomeSausage'] + + @cookbook = Chef::CookbookVersion.new('cookbook_name') + + @cookbook_loader = mock('Chef::CookbookLoader') + @cookbook_loader.stub!(:cookbook_exists?).and_return(true) + @cookbook_loader.stub!(:[]).and_return(@cookbook) + Chef::CookbookLoader.stub!(:new).and_return(@cookbook_loader) + + @cookbook_uploader = Chef::CookbookUploader.new('herpderp', File.join(CHEF_SPEC_DATA, 'cookbooks'), :rest => "norest") + Chef::CookbookUploader.stub!(:new).and_return(@cookbook_uploader) + @cookbook_uploader.stub!(:validate_cookbooks).and_return(true) + Chef::CookbookSiteStreamingUploader.stub!(:create_build_dir).and_return(Dir.mktmpdir) + + Chef::Mixin::Command.stub(:run_command).and_return(true) + @stdout = StringIO.new + @knife.ui.stub!(:stdout).and_return(@stdout) + end + + describe 'run' do + + before(:each) do + @knife.stub!(:do_upload).and_return(true) + end + + it 'should should print usage and exit when given no arguments' do + @knife.name_args = [] + @knife.should_receive(:show_usage) + @knife.ui.should_receive(:fatal) + lambda { @knife.run }.should raise_error(SystemExit) + end + + it 'should print usage and exit when given only 1 argument' do + @knife.name_args = ['cookbook_name'] + @knife.should_receive(:show_usage) + @knife.ui.should_receive(:fatal) + lambda { @knife.run }.should raise_error(SystemExit) + end + + it 'should check if the cookbook exists' do + @cookbook_loader.should_receive(:cookbook_exists?) + @knife.run + end + + it "should exit and log to error if the cookbook doesn't exist" do + @cookbook_loader.stub(:cookbook_exists?).and_return(false) + @knife.ui.should_receive(:error) + lambda { @knife.run }.should raise_error(SystemExit) + end + + it 'should make a tarball of the cookbook' do + Chef::Mixin::Command.should_receive(:run_command) { |args| + args[:command].should match /tar -czf/ + } + @knife.run + end + + it 'should exit and log to error when the tarball creation fails' do + Chef::Mixin::Command.stub!(:run_command).and_raise(Chef::Exceptions::Exec) + @knife.ui.should_receive(:error) + lambda { @knife.run }.should raise_error(SystemExit) + end + + it 'should upload the cookbook and clean up the tarball' do + @knife.should_receive(:do_upload) + FileUtils.should_receive(:rm_rf) + @knife.run + end + end + + describe 'do_upload' do + + before(:each) do + @upload_response = mock('Net::HTTPResponse') + Chef::CookbookSiteStreamingUploader.stub!(:post).and_return(@upload_response) + + @stdout = StringIO.new + @stderr = StringIO.new + @knife.ui.stub!(:stdout).and_return(@stdout) + @knife.ui.stub!(:stderr).and_return(@stderr) + File.stub(:open).and_return(true) + end + + it 'should post the cookbook to "http://cookbooks.opscode.com"' do + response_text = {:uri => 'http://cookbooks.opscode.com/cookbooks/cookbook_name'}.to_json + @upload_response.stub!(:body).and_return(response_text) + @upload_response.stub!(:code).and_return(201) + Chef::CookbookSiteStreamingUploader.should_receive(:post).with(/cookbooks\.opscode\.com/, anything(), anything(), anything()) + @knife.run + end + + it 'should alert the user when a version already exists' do + response_text = {:error_messages => ['Version already exists']}.to_json + @upload_response.stub!(:body).and_return(response_text) + @upload_response.stub!(:code).and_return(409) + lambda { @knife.run }.should raise_error(SystemExit) + @stderr.string.should match(/ERROR(.+)cookbook already exists/) + end + + it 'should pass any errors on to the user' do + response_text = {:error_messages => ["You're holding it wrong"]}.to_json + @upload_response.stub!(:body).and_return(response_text) + @upload_response.stub!(:code).and_return(403) + lambda { @knife.run }.should raise_error(SystemExit) + @stderr.string.should match("ERROR(.*)You're holding it wrong") + end + + it 'should print the body if no errors are exposed on failure' do + response_text = {:system_error => "Your call was dropped", :reason => "There's a map for that"}.to_json + @upload_response.stub!(:body).and_return(response_text) + @upload_response.stub!(:code).and_return(500) + @knife.ui.should_receive(:error).with(/#{Regexp.escape(response_text)}/)#.ordered + @knife.ui.should_receive(:error).with(/Unknown error/)#.ordered + lambda { @knife.run }.should raise_error(SystemExit) + end + + end + +end diff --git a/spec/unit/knife/cookbook_site_unshare_spec.rb b/spec/unit/knife/cookbook_site_unshare_spec.rb new file mode 100644 index 0000000000..ffba2ec664 --- /dev/null +++ b/spec/unit/knife/cookbook_site_unshare_spec.rb @@ -0,0 +1,77 @@ +# +# Author:: Stephen Delano (<stephen@opscode.com>) +# Author:: Tim Hinderliter (<tim@opscode.com>) +# Copyright:: Copyright (c) 2010 Opscode, 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::CookbookSiteUnshare do + + before(:each) do + @knife = Chef::Knife::CookbookSiteUnshare.new + @knife.name_args = ['cookbook_name'] + @knife.stub!(:confirm).and_return(true) + + @rest = mock('Chef::REST') + @rest.stub!(:delete_rest).and_return(true) + @knife.stub!(:rest).and_return(@rest) + @stdout = StringIO.new + @knife.ui.stub!(:stdout).and_return(@stdout) + end + + describe 'run' do + + describe 'with no cookbook argument' do + it 'should print the usage and exit' do + @knife.name_args = [] + @knife.ui.should_receive(:fatal) + @knife.should_receive(:show_usage) + lambda { @knife.run }.should raise_error(SystemExit) + end + end + + it 'should confirm you want to unshare the cookbook' do + @knife.should_receive(:confirm) + @knife.run + end + + it 'should send a delete request to the cookbook site' do + @rest.should_receive(:delete_rest) + @knife.run + end + + it 'should log an error and exit when forbidden' do + exception = mock('403 "Forbidden"', :code => '403') + @rest.stub!(:delete_rest).and_raise(Net::HTTPServerException.new('403 "Forbidden"', exception)) + @knife.ui.should_receive(:error) + lambda { @knife.run }.should raise_error(SystemExit) + end + + it 'should re-raise any non-forbidden errors on delete_rest' do + exception = mock('500 "Application Error"', :code => '500') + @rest.stub(:delete_rest).and_raise(Net::HTTPServerException.new('500 "Application Error"', exception)) + lambda { @knife.run }.should raise_error(Net::HTTPServerException) + end + + it 'should log a success message' do + @knife.ui.should_receive(:info) + @knife.run + end + + end + +end diff --git a/spec/unit/knife/cookbook_test_spec.rb b/spec/unit/knife/cookbook_test_spec.rb new file mode 100644 index 0000000000..24c658dc6c --- /dev/null +++ b/spec/unit/knife/cookbook_test_spec.rb @@ -0,0 +1,84 @@ +# +# Author:: Stephen Delano (<stephen@opscode.com>)$ +# Author:: Matthew Kent (<mkent@magoazul.com>) +# Copyright:: Copyright (c) 2010 Opscode, Inc.$ +# Copyright:: Copyright (c) 2010 Matthew Kent +# 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' +Chef::Knife::CookbookTest.load_deps + +describe Chef::Knife::CookbookTest do + before(:each) do + Chef::Config[:node_name] = "webmonkey.example.com" + @knife = Chef::Knife::CookbookTest.new + @knife.config[:cookbook_path] = File.join(CHEF_SPEC_DATA,'cookbooks') + @knife.cookbook_loader.stub!(:cookbook_exists?).and_return(true) + @cookbooks = [] + %w{tats central_market jimmy_johns pho}.each do |cookbook_name| + @cookbooks << Chef::CookbookVersion.new(cookbook_name) + end + @stdout = StringIO.new + @knife.ui.stub!(:stdout).and_return(@stdout) + end + + describe "run" do + it "should test the cookbook" do + @knife.stub!(:test_cookbook).and_return(true) + @knife.name_args = ["italian"] + @knife.should_receive(:test_cookbook).with("italian") + @knife.run + end + + it "should test multiple cookbooks when provided" do + @knife.stub!(:test_cookbook).and_return(true) + @knife.name_args = ["tats", "jimmy_johns"] + @knife.should_receive(:test_cookbook).with("tats") + @knife.should_receive(:test_cookbook).with("jimmy_johns") + @knife.should_not_receive(:test_cookbook).with("central_market") + @knife.should_not_receive(:test_cookbook).with("pho") + @knife.run + end + + it "should test both ruby and templates" do + @knife.name_args = ["example"] + @knife.config[:cookbook_path].should_not be_empty + Array(@knife.config[:cookbook_path]).reverse.each do |path| + @knife.should_receive(:test_ruby).with(an_instance_of(Chef::Cookbook::SyntaxCheck)) + @knife.should_receive(:test_templates).with(an_instance_of(Chef::Cookbook::SyntaxCheck)) + end + @knife.run + end + + describe "with -a or --all" do + it "should test all of the cookbooks" do + @knife.stub!(:test_cookbook).and_return(true) + @knife.config[:all] = true + @loader = {} + @loader.stub!(:load_cookbooks).and_return(@loader) + @cookbooks.each do |cookbook| + @loader[cookbook.name] = cookbook + end + @knife.stub!(:cookbook_loader).and_return(@loader) + @loader.each do |key, cookbook| + @knife.should_receive(:test_cookbook).with(cookbook.name) + end + @knife.run + end + end + + end +end diff --git a/spec/unit/knife/cookbook_upload_spec.rb b/spec/unit/knife/cookbook_upload_spec.rb new file mode 100644 index 0000000000..4659e60371 --- /dev/null +++ b/spec/unit/knife/cookbook_upload_spec.rb @@ -0,0 +1,183 @@ +# +# Author:: Matthew Kent (<mkent@magoazul.com>) +# Author:: Steven Danna (<steve@opscode.com>) +# Copyright:: Copyright (c) 2012 Opscode, 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper")) + +require 'chef/cookbook_uploader' +require 'timeout' + +describe Chef::Knife::CookbookUpload do + before(:each) do + @knife = Chef::Knife::CookbookUpload.new + @knife.name_args = ['test_cookbook'] + + @cookbook = Chef::CookbookVersion.new('test_cookbook') + + @cookbook_loader = {} + @cookbook_loader.stub!(:[]).and_return(@cookbook) + @cookbook_loader.stub!(:merged_cookbooks).and_return([]) + @cookbook_loader.stub!(:load_cookbooks).and_return(@cookbook_loader) + Chef::CookbookLoader.stub!(:new).and_return(@cookbook_loader) + + @output = StringIO.new + @knife.ui.stub!(:stdout).and_return(@output) + @knife.ui.stub!(:stderr).and_return(@output) + end + + describe 'run' do + before(:each) do + @knife.stub!(:upload).and_return(true) + Chef::CookbookVersion.stub(:list_all_versions).and_return({}) + end + + it 'should print usage and exit when a cookbook name is not provided' do + @knife.name_args = [] + @knife.should_receive(:show_usage) + @knife.ui.should_receive(:fatal) + lambda { @knife.run }.should raise_error(SystemExit) + end + + describe 'when specifying a cookbook name' do + it 'should upload the cookbook' do + @knife.should_receive(:upload).once + @knife.run + end + + it 'should report on success' do + @knife.should_receive(:upload).once + @knife.ui.should_receive(:info).with(/Uploaded 1 cookbook/) + @knife.run + end + end + + describe 'when specifying the same cookbook name twice' do + it 'should upload the cookbook only once' do + @knife.name_args = ['test_cookbook', 'test_cookbook'] + @knife.should_receive(:upload).once + @knife.run + end + end + + describe 'when specifying a cookbook name among many' do + before(:each) do + @knife.name_args = ['test_cookbook1'] + @cookbooks = { + 'test_cookbook1' => Chef::CookbookVersion.new('test_cookbook1'), + 'test_cookbook2' => Chef::CookbookVersion.new('test_cookbook2'), + 'test_cookbook3' => Chef::CookbookVersion.new('test_cookbook3') + } + @cookbook_loader = {} + @cookbook_loader.stub!(:merged_cookbooks).and_return([]) + @cookbook_loader.stub(:[]) { |ckbk| @cookbooks[ckbk] } + Chef::CookbookLoader.stub!(:new).and_return(@cookbook_loader) + end + + it "should read only one cookbook" do + @cookbook_loader.should_receive(:[]).once.with('test_cookbook1') + @knife.run + end + + it "should not read all cookbooks" do + @cookbook_loader.should_not_receive(:load_cookbooks) + @knife.run + end + + it "should upload only one cookbook" do + @knife.should_receive(:upload).exactly(1).times + @knife.run + end + end + + # This is testing too much. We should break it up. + describe 'when specifying a cookbook name with dependencies' do + it "should upload all dependencies once" do + @knife.name_args = ["test_cookbook2"] + @knife.config[:depends] = true + @test_cookbook1 = Chef::CookbookVersion.new('test_cookbook1') + @test_cookbook2 = Chef::CookbookVersion.new('test_cookbook2') + @test_cookbook3 = Chef::CookbookVersion.new('test_cookbook3') + @test_cookbook2.metadata.depends("test_cookbook3") + @test_cookbook3.metadata.depends("test_cookbook1") + @test_cookbook3.metadata.depends("test_cookbook2") + @cookbook_loader.stub!(:[]) do |ckbk| + { "test_cookbook1" => @test_cookbook1, + "test_cookbook2" => @test_cookbook2, + "test_cookbook3" => @test_cookbook3 }[ckbk] + end + @knife.stub!(:cookbook_names).and_return(["test_cookbook1", "test_cookbook2", "test_cookbook3"]) + @knife.should_receive(:upload).exactly(3).times + Timeout::timeout(5) do + @knife.run + end.should_not raise_error(Timeout::Error) + end + end + + it "should freeze the version of the cookbooks if --freeze is specified" do + @knife.config[:freeze] = true + @cookbook.should_receive(:freeze_version).once + @knife.run + end + + describe 'with -a or --all' do + before(:each) do + @knife.config[:all] = true + @test_cookbook1 = Chef::CookbookVersion.new('test_cookbook1') + @test_cookbook2 = Chef::CookbookVersion.new('test_cookbook2') + @cookbook_loader.stub!(:each).and_yield("test_cookbook1", @test_cookbook1).and_yield("test_cookbook2", @test_cookbook2) + @cookbook_loader.stub!(:cookbook_names).and_return(["test_cookbook1", "test_cookbook2"]) + end + + it 'should upload all cookbooks' do + @knife.should_receive(:upload).once + @knife.run + end + + it 'should report on success' do + @knife.should_receive(:upload).once + @knife.ui.should_receive(:info).with(/Uploaded all cookbooks/) + @knife.run + end + + it 'should update the version constraints for an environment' do + @knife.stub!(:assert_environment_valid!).and_return(true) + @knife.config[:environment] = "production" + @knife.should_receive(:update_version_constraints).once + @knife.run + end + end + + describe 'when a frozen cookbook exists on the server' do + it 'should fail to replace it' do + @knife.stub!(:upload).and_raise(Chef::Exceptions::CookbookFrozen) + @knife.ui.should_receive(:error).with(/Failed to upload 1 cookbook/) + lambda { @knife.run }.should raise_error(SystemExit) + end + + it 'should not update the version constraints for an environment' do + @knife.stub!(:assert_environment_valid!).and_return(true) + @knife.config[:environment] = "production" + @knife.stub!(:upload).and_raise(Chef::Exceptions::CookbookFrozen) + @knife.ui.should_receive(:error).with(/Failed to upload 1 cookbook/) + @knife.ui.should_receive(:warn).with(/Not updating version constraints/) + @knife.should_not_receive(:update_version_constraints) + lambda { @knife.run }.should raise_error(SystemExit) + end + end + end # run +end # Chef::Knife::CookbookUpload diff --git a/spec/unit/knife/core/bootstrap_context_spec.rb b/spec/unit/knife/core/bootstrap_context_spec.rb new file mode 100644 index 0000000000..f8a58484a5 --- /dev/null +++ b/spec/unit/knife/core/bootstrap_context_spec.rb @@ -0,0 +1,128 @@ +# +# Author:: Daniel DeLeo (<dan@opscode.com>) +# Copyright:: Copyright (c) 2011 Opscode, 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' +require 'chef/knife/core/bootstrap_context' + +describe Chef::Knife::Core::BootstrapContext do + before do + @config = {:foo => :bar} + @run_list = Chef::RunList.new('recipe[tmux]', 'role[base]') + @chef_config = {:validation_key => File.join(CHEF_SPEC_DATA, 'ssl', 'private_key.pem')} + @chef_config[:chef_server_url] = 'http://chef.example.com:4444' + @chef_config[:validation_client_name] = 'chef-validator-testing' + @context = Chef::Knife::Core::BootstrapContext.new(@config, @run_list, @chef_config) + end + + describe "to support compatability with existing templates" do + it "sets the @config instance variable" do + @context.instance_eval { @config }.should == {:foo => :bar} + end + + it "sets the @run_list instance variable" do + @context.instance_eval { @run_list }.should equal(@run_list) + end + end + + it "installs the same version of chef on the remote host" do + @context.bootstrap_version_string.should == "--version #{Chef::VERSION}" + end + + it "runs chef with the first-boot.json in the _default environment" do + @context.start_chef.should == "chef-client -j /etc/chef/first-boot.json -E _default" + end + + it "it runs chef-client from another path when specified" do + @chef_config[:chef_client_path] = '/usr/local/bin/chef-client' + @context.start_chef.should == "/usr/local/bin/chef-client -j /etc/chef/first-boot.json -E _default" + end + + it "reads the validation key" do + @context.validation_key.should == IO.read(File.join(CHEF_SPEC_DATA, 'ssl', 'private_key.pem')) + end + + it "generates the config file data" do + expected=<<-EXPECTED +log_level :info +log_location STDOUT +chef_server_url "http://chef.example.com:4444" +validation_client_name "chef-validator-testing" +# Using default node name (fqdn) +EXPECTED + @context.config_content.should == expected + end + + describe "when an explicit node name is given" do + before do + @config[:chef_node_name] = 'foobar.example.com' + end + it "sets the node name in the client.rb" do + @context.config_content.should match(/node_name "foobar\.example\.com"/) + end + end + + describe "when bootstrapping into a specific environment" do + before do + @chef_config[:environment] = "prodtastic" + end + + it "starts chef in the configured environment" do + @context.start_chef.should == 'chef-client -j /etc/chef/first-boot.json -E prodtastic' + end + end + + describe "when installing a prerelease version of chef" do + before do + @config[:prerelease] = true + end + it "supplies --prerelease as the version string" do + @context.bootstrap_version_string.should == '--prerelease' + end + end + + describe "when installing an explicit version of chef" do + before do + @context = Chef::Knife::Core::BootstrapContext.new(@config, @run_list, :knife => { :bootstrap_version => '123.45.678' }) + end + + it "gives --version $VERSION as the version string" do + @context.bootstrap_version_string.should == '--version 123.45.678' + end + end + + describe "when JSON attributes are given" do + before do + conf = @config.dup + conf[:first_boot_attributes] = {:baz => :quux} + @context = Chef::Knife::Core::BootstrapContext.new(conf, @run_list, @chef_config) + end + + it "adds the attributes to first_boot" do + @context.first_boot.to_json.should == {:baz => :quux, :run_list => @run_list}.to_json + end + end + + describe "when JSON attributes are NOT given" do + it "sets first_boot equal to run_list" do + @context.first_boot.to_json.should == {:run_list => @run_list}.to_json + end + end + + +end + diff --git a/spec/unit/knife/core/cookbook_scm_repo_spec.rb b/spec/unit/knife/core/cookbook_scm_repo_spec.rb new file mode 100644 index 0000000000..629164ad0a --- /dev/null +++ b/spec/unit/knife/core/cookbook_scm_repo_spec.rb @@ -0,0 +1,187 @@ +# +# Author:: Daniel DeLeo (<dan@opscode.com>) +# Copyright:: Copyright (c) 2011 Opscode, 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' +require 'chef/knife/core/cookbook_scm_repo' + +describe Chef::Knife::CookbookSCMRepo do + before do + @repo_path = File.join(CHEF_SPEC_DATA, 'cookbooks') + @stdout, @stderr, @stdin = StringIO.new, StringIO.new, StringIO.new + @ui = Chef::Knife::UI.new(@stdout, @stderr, @stdin, {}) + @cookbook_repo = Chef::Knife::CookbookSCMRepo.new(@repo_path, @ui, :default_branch => 'master') + + @branch_list = Mixlib::ShellOut.new + @branch_list.stdout.replace(<<-BRANCHES) + chef-vendor-apache2 + chef-vendor-build-essential + chef-vendor-dynomite + chef-vendor-ganglia + chef-vendor-graphite + chef-vendor-python + chef-vendor-absent-new +BRANCHES + end + + it "has a path to the cookbook repo" do + @cookbook_repo.repo_path.should == @repo_path + end + + it "has a default branch" do + @cookbook_repo.default_branch.should == 'master' + end + + describe "when sanity checking the repo" do + it "exits when the directory does not exist" do + ::File.should_receive(:directory?).with(@repo_path).and_return(false) + lambda {@cookbook_repo.sanity_check}.should raise_error(SystemExit) + end + + describe "and the repo dir exists" do + before do + ::File.stub!(:directory?).with(@repo_path).and_return(true) + end + + it "exits when there is no git repo" do + ::File.stub!(:directory?).with(/.*\.git/).and_return(false) + lambda {@cookbook_repo.sanity_check}.should raise_error(SystemExit) + end + + describe "and the repo is a git repo" do + before do + ::File.stub!(:directory?).with(File.join(@repo_path, '.git')).and_return(true) + end + + it "exits when the default branch doesn't exist" do + @nobranches = Mixlib::ShellOut.new.tap {|s|s.stdout.replace "\n"} + @cookbook_repo.should_receive(:shell_out!).with('git branch --no-color', :cwd => @repo_path).and_return(@nobranches) + lambda {@cookbook_repo.sanity_check}.should raise_error(SystemExit) + end + + describe "and the default branch exists" do + before do + @master_branch = Mixlib::ShellOut.new + @master_branch.stdout.replace "* master\n" + @cookbook_repo.should_receive(:shell_out!).with("git branch --no-color", :cwd => @repo_path).and_return(@master_branch) + end + + it "exits when the git repo is dirty" do + @dirty_status = Mixlib::ShellOut.new + @dirty_status.stdout.replace(<<-DIRTY) + M chef/lib/chef/knife/cookbook_site_vendor.rb +DIRTY + @cookbook_repo.should_receive(:shell_out!).with('git status --porcelain', :cwd => @repo_path).and_return(@dirty_status) + lambda {@cookbook_repo.sanity_check}.should raise_error(SystemExit) + end + + describe "and the repo is clean" do + before do + @clean_status = Mixlib::ShellOut.new.tap {|s| s.stdout.replace("\n")} + @cookbook_repo.stub!(:shell_out!).with('git status --porcelain', :cwd => @repo_path).and_return(@clean_status) + end + + it "passes the sanity check" do + @cookbook_repo.sanity_check + end + + end + end + end + end + end + + it "resets to default state by checking out the default branch" do + @cookbook_repo.should_receive(:shell_out!).with('git checkout master', :cwd => @repo_path) + @cookbook_repo.reset_to_default_state + end + + it "determines if a the pristine copy branch exists" do + @cookbook_repo.should_receive(:shell_out!).with('git branch --no-color', :cwd => @repo_path).and_return(@branch_list) + @cookbook_repo.branch_exists?("chef-vendor-apache2").should be_true + @cookbook_repo.should_receive(:shell_out!).with('git branch --no-color', :cwd => @repo_path).and_return(@branch_list) + @cookbook_repo.branch_exists?("chef-vendor-nginx").should be_false + end + + it "determines if a the branch not exists correctly without substring search" do + @cookbook_repo.should_receive(:shell_out!).twice.with('git branch --no-color', :cwd => @repo_path).and_return(@branch_list) + @cookbook_repo.should_not be_branch_exists("chef-vendor-absent") + @cookbook_repo.should be_branch_exists("chef-vendor-absent-new") + end + + describe "when the pristine copy branch does not exist" do + it "prepares for import by creating the pristine copy branch" do + @cookbook_repo.should_receive(:shell_out!).with('git branch --no-color', :cwd => @repo_path).and_return(@branch_list) + @cookbook_repo.should_receive(:shell_out!).with('git checkout -b chef-vendor-nginx', :cwd => @repo_path) + @cookbook_repo.prepare_to_import("nginx") + end + end + + describe "when the pristine copy branch does exist" do + it "prepares for import by checking out the pristine copy branch" do + @cookbook_repo.should_receive(:shell_out!).with('git branch --no-color', :cwd => @repo_path).and_return(@branch_list) + @cookbook_repo.should_receive(:shell_out!).with('git checkout chef-vendor-apache2', :cwd => @repo_path) + @cookbook_repo.prepare_to_import("apache2") + end + end + + describe "when the pristine copy branch was not updated by the changes" do + before do + @updates = Mixlib::ShellOut.new + @updates.stdout.replace("\n") + @cookbook_repo.stub!(:shell_out!).with('git status --porcelain -- apache2', :cwd => @repo_path).and_return(@updates) + end + + it "shows no changes in the pristine copy" do + @cookbook_repo.updated?('apache2').should be_false + end + + it "does nothing to finalize the updates" do + @cookbook_repo.finalize_updates_to('apache2', '1.2.3').should be_false + end + end + + describe "when the pristine copy branch was updated by the changes" do + before do + @updates = Mixlib::ShellOut.new + @updates.stdout.replace(" M cookbooks/apache2/recipes/default.rb\n") + @cookbook_repo.stub!(:shell_out!).with('git status --porcelain -- apache2', :cwd => @repo_path).and_return(@updates) + end + + it "shows changes in the pristine copy" do + @cookbook_repo.updated?('apache2').should be_true + end + + it "commits the changes to the repo and tags the commit" do + @cookbook_repo.should_receive(:shell_out!).with("git add apache2", :cwd => @repo_path) + @cookbook_repo.should_receive(:shell_out!).with("git commit -m \"Import apache2 version 1.2.3\" -- apache2", :cwd => @repo_path) + @cookbook_repo.should_receive(:shell_out!).with("git tag -f cookbook-site-imported-apache2-1.2.3", :cwd => @repo_path) + @cookbook_repo.finalize_updates_to("apache2", "1.2.3").should be_true + end + end + + describe "when a custom default branch is specified" do + before do + @cookbook_repo = Chef::Knife::CookbookSCMRepo.new(@repo_path, @ui, :default_branch => 'develop') + end + + it "resets to default state by checking out the default branch" do + @cookbook_repo.should_receive(:shell_out!).with('git checkout develop', :cwd => @repo_path) + @cookbook_repo.reset_to_default_state + end + end +end diff --git a/spec/unit/knife/core/object_loader_spec.rb b/spec/unit/knife/core/object_loader_spec.rb new file mode 100644 index 0000000000..b3456e2b15 --- /dev/null +++ b/spec/unit/knife/core/object_loader_spec.rb @@ -0,0 +1,81 @@ +# +# Author:: Daniel DeLeo (<dan@opscode.com>) +# Author:: Juanje Ojeda (<juanje.ojeda@gmail.com>) +# Copyright:: Copyright (c) 2011-2012 Opscode, 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' +require 'chef/knife/core/object_loader' + +describe Chef::Knife::Core::ObjectLoader do + before(:each) do + @knife = Chef::Knife.new + @stdout = StringIO.new + @knife.ui.stub!(:stdout).and_return(@stdout) + Dir.chdir(File.join(CHEF_SPEC_DATA, 'object_loader')) + end + + shared_examples_for "Chef object" do |chef_class| + it "should create a #{chef_class} object" do + @object.should be_a_kind_of(chef_class) + end + + it "should has a attribute 'name'" do + @object.name.should eql('test') + end + end + + { + 'nodes' => Chef::Node, + 'roles' => Chef::Role, + 'environments' => Chef::Environment + }.each do |repo_location, chef_class| + + describe "when the file is a #{chef_class}" do + before do + @loader = Chef::Knife::Core::ObjectLoader.new(chef_class, @knife.ui) + end + + describe "when the file is a Ruby" do + before do + @object = @loader.load_from(repo_location, 'test.rb') + end + + it_behaves_like "Chef object", chef_class + end + + #NOTE: This is check for the bug described at CHEF-2352 + describe "when the file is a JSON" do + describe "and it has defined 'json_class'" do + before do + @object = @loader.load_from(repo_location, 'test_json_class.json') + end + + it_behaves_like "Chef object", chef_class + end + + describe "and it has not defined 'json_class'" do + before do + @object = @loader.load_from(repo_location, 'test.json') + end + + it_behaves_like "Chef object", chef_class + end + end + end + end + +end diff --git a/spec/unit/knife/core/subcommand_loader_spec.rb b/spec/unit/knife/core/subcommand_loader_spec.rb new file mode 100644 index 0000000000..e39e0be041 --- /dev/null +++ b/spec/unit/knife/core/subcommand_loader_spec.rb @@ -0,0 +1,54 @@ +# +# Author:: Daniel DeLeo (<dan@opscode.com>) +# Copyright:: Copyright (c) 2011 Opscode, 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::SubcommandLoader do + before do + @home = File.join(CHEF_SPEC_DATA, 'knife-home') + @env = {'HOME' => @home} + @loader = Chef::Knife::SubcommandLoader.new(File.join(CHEF_SPEC_DATA, 'knife-site-subcommands'), @env) + end + + it "builds a list of the core subcommand file require paths" do + @loader.subcommand_files.should_not be_empty + @loader.subcommand_files.each do |require_path| + require_path.should match(/chef\/knife\/.*|plugins\/knife\/.*/) + end + end + + it "finds files installed via rubygems" do + @loader.find_subcommands_via_rubygems.should include('chef/knife/node_create') + @loader.find_subcommands_via_rubygems.each {|rel_path, abs_path| abs_path.should match(%r[chef/knife/.+])} + end + + it "finds files using a dirglob when rubygems is not available" do + @loader.find_subcommands_via_dirglob.should include('chef/knife/node_create') + @loader.find_subcommands_via_dirglob.each {|rel_path, abs_path| abs_path.should match(%r[chef/knife/.+])} + end + + it "finds user-specific subcommands in the user's ~/.chef directory" do + expected_command = File.join(@home, '.chef', 'plugins', 'knife', 'example_home_subcommand.rb') + @loader.site_subcommands.should include(expected_command) + end + + it "finds repo specific subcommands by searching for a .chef directory" do + expected_command = File.join(CHEF_SPEC_DATA, 'knife-site-subcommands', 'plugins', 'knife', 'example_subcommand.rb') + @loader.site_subcommands.should include(expected_command) + end +end diff --git a/spec/unit/knife/core/ui_spec.rb b/spec/unit/knife/core/ui_spec.rb new file mode 100644 index 0000000000..784ad1f0d7 --- /dev/null +++ b/spec/unit/knife/core/ui_spec.rb @@ -0,0 +1,309 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Author:: Tim Hinderliter (<tim@opscode.com>) +# Author:: Daniel DeLeo (<dan@opscode.com>) +# Author:: John Keiser (<jkeiser@opscode.com>) +# Copyright:: Copyright (c) 2008, 2011, 2012 Opscode, 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::UI do + before do + @out, @err, @in = StringIO.new, StringIO.new, StringIO.new + @config = {} + @ui = Chef::Knife::UI.new(@out, @err, @in, @config) + end + + describe "format_list_for_display" do + it "should print the full hash if --with-uri is true" do + @ui.config[:with_uri] = true + @ui.format_list_for_display({ :marcy => :playground }).should == { :marcy => :playground } + end + + it "should print only the keys if --with-uri is false" do + @ui.config[:with_uri] = false + @ui.format_list_for_display({ :marcy => :playground }).should == [ :marcy ] + end + end + + describe "output" do + it "formats strings appropriately" do + @ui.output("hi") + @out.string.should == "hi\n" + end + + it "formats hashes appropriately" do + @ui.output({'hi' => 'a', 'lo' => 'b' }) + @out.string.should == <<EOM +hi: a +lo: b +EOM + end + + it "formats empty hashes appropriately" do + @ui.output({}) + @out.string.should == "\n" + end + + it "formats arrays appropriately" do + @ui.output([ 'a', 'b' ]) + @out.string.should == <<EOM +a +b +EOM + end + + it "formats empty arrays appropriately" do + @ui.output([ ]) + @out.string.should == "\n" + end + + it "formats single-member arrays appropriately" do + @ui.output([ 'a' ]) + @out.string.should == "a\n" + end + + it "formats nested single-member arrays appropriately" do + @ui.output([ [ 'a' ] ]) + @out.string.should == "a\n" + end + + it "formats nested arrays appropriately" do + @ui.output([ [ 'a', 'b' ], [ 'c', 'd' ]]) + @out.string.should == <<EOM +a +b + +c +d +EOM + end + + it "formats nested arrays with single- and empty subarrays appropriately" do + @ui.output([ [ 'a', 'b' ], [ 'c' ], [], [ 'd', 'e' ]]) + @out.string.should == <<EOM +a +b + +c + + +d +e +EOM + end + + it "formats arrays of hashes with extra lines in between for readability" do + @ui.output([ { 'a' => 'b', 'c' => 'd' }, { 'x' => 'y' }, { 'm' => 'n', 'o' => 'p' }]) + @out.string.should == <<EOM +a: b +c: d + +x: y + +m: n +o: p +EOM + end + + it "formats hashes with empty array members appropriately" do + @ui.output({ 'a' => [], 'b' => 'c' }) + @out.string.should == <<EOM +a: +b: c +EOM + end + + it "formats hashes with single-member array values appropriately" do + @ui.output({ 'a' => [ 'foo' ], 'b' => 'c' }) + @out.string.should == <<EOM +a: foo +b: c +EOM + end + + it "formats hashes with array members appropriately" do + @ui.output({ 'a' => [ 'foo', 'bar' ], 'b' => 'c' }) + @out.string.should == <<EOM +a: + foo + bar +b: c +EOM + end + + it "formats hashes with single-member nested array values appropriately" do + @ui.output({ 'a' => [ [ 'foo' ] ], 'b' => 'c' }) + @out.string.should == <<EOM +a: + foo +b: c +EOM + end + + it "formats hashes with nested array values appropriately" do + @ui.output({ 'a' => [ [ 'foo', 'bar' ], [ 'baz', 'bjork' ] ], 'b' => 'c' }) + @out.string.should == <<EOM +a: + foo + bar + + baz + bjork +b: c +EOM + end + + it "formats hashes with hash values appropriately" do + @ui.output({ 'a' => { 'aa' => 'bb', 'cc' => 'dd' }, 'b' => 'c' }) + @out.string.should == <<EOM +a: + aa: bb + cc: dd +b: c +EOM + end + + it "formats hashes with empty hash values appropriately" do + @ui.output({ 'a' => { }, 'b' => 'c' }) + @out.string.should == <<EOM +a: +b: c +EOM + end + end + + describe "format_for_display" do + it "should return the raw data" do + input = { :gi => :go } + @ui.format_for_display(input).should == input + end + + describe "with --attribute passed" do + it "should return the deeply nested attribute" do + input = { "gi" => { "go" => "ge" }, "id" => "sample-data-bag-item" } + @ui.config[:attribute] = "gi.go" + @ui.format_for_display(input).should == { "sample-data-bag-item" => { "gi.go" => "ge" } } + end + end + + describe "with --run-list passed" do + it "should return the run list" do + input = Chef::Node.new + input.name("sample-node") + input.run_list("role[monkey]", "role[churchmouse]") + @ui.config[:run_list] = true + response = @ui.format_for_display(input) + response["sample-node"]["run_list"][0].should == "role[monkey]" + response["sample-node"]["run_list"][1].should == "role[churchmouse]" + end + end + end + + describe "format_cookbook_list_for_display" do + before(:each) do + @item = { + "cookbook_name" => { + "url" => "http://url/cookbooks/cookbook", + "versions" => [ + { "version" => "3.0.0", "url" => "http://url/cookbooks/3.0.0" }, + { "version" => "2.0.0", "url" => "http://url/cookbooks/2.0.0" }, + { "version" => "1.0.0", "url" => "http://url/cookbooks/1.0.0" } + ] + } + } + end + + it "should return an array of the cookbooks with versions" do + expected_response = [ "cookbook_name 3.0.0 2.0.0 1.0.0" ] + response = @ui.format_cookbook_list_for_display(@item) + response.should == expected_response + end + + describe "with --with-uri" do + it "should return the URIs" do + response = { + "cookbook_name"=>{ + "1.0.0" => "http://url/cookbooks/1.0.0", + "2.0.0" => "http://url/cookbooks/2.0.0", + "3.0.0" => "http://url/cookbooks/3.0.0"} + } + @ui.config[:with_uri] = true + @ui.format_cookbook_list_for_display(@item).should == response + end + end + end + + describe "confirm" do + before(:each) do + @question = "monkeys rule" + @stdout = StringIO.new + @ui.stub(:stdout).and_return(@stdout) + @ui.stdin.stub!(:readline).and_return("y") + end + + it "should return true if you answer Y" do + @ui.stdin.stub!(:readline).and_return("Y") + @ui.confirm(@question).should == true + end + + it "should return true if you answer y" do + @ui.stdin.stub!(:readline).and_return("y") + @ui.confirm(@question).should == true + end + + it "should exit 3 if you answer N" do + @ui.stdin.stub!(:readline).and_return("N") + lambda { + @ui.confirm(@question) + }.should raise_error(SystemExit) { |e| e.status.should == 3 } + end + + it "should exit 3 if you answer n" do + @ui.stdin.stub!(:readline).and_return("n") + lambda { + @ui.confirm(@question) + }.should raise_error(SystemExit) { |e| e.status.should == 3 } + end + + describe "with --y or --yes passed" do + it "should return true" do + @ui.config[:yes] = true + @ui.confirm(@question).should == true + end + end + + describe "when asking for free-form user input" do + it "asks a question and returns the answer provided by the user" do + out = StringIO.new + @ui.stub!(:stdout).and_return(out) + @ui.stub!(:stdin).and_return(StringIO.new("http://mychefserver.example.com\n")) + @ui.ask_question("your chef server URL?").should == "http://mychefserver.example.com" + out.string.should == "your chef server URL?" + end + + it "suggests a default setting and returns the default when the user's response only contains whitespace" do + out = StringIO.new + @ui.stub!(:stdout).and_return(out) + @ui.stub!(:stdin).and_return(StringIO.new(" \n")) + @ui.ask_question("your chef server URL? ", :default => 'http://localhost:4000').should == "http://localhost:4000" + out.string.should == "your chef server URL? [http://localhost:4000] " + end + end + + end +end diff --git a/spec/unit/knife/data_bag_create_spec.rb b/spec/unit/knife/data_bag_create_spec.rb new file mode 100644 index 0000000000..7d9433984f --- /dev/null +++ b/spec/unit/knife/data_bag_create_spec.rb @@ -0,0 +1,105 @@ +# +# Author:: Daniel DeLeo (<dan@opscode.com>) +# Author:: Seth Falcon (<seth@opscode.com>) +# Copyright:: Copyright (c) 2009-2010 Opscode, 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' +require 'tempfile' + +module ChefSpecs + class ChefRest + attr_reader :args_received + def initialize + @args_received = [] + end + + def post_rest(*args) + @args_received << args + end + end +end + + +describe Chef::Knife::DataBagCreate do + before do + Chef::Config[:node_name] = "webmonkey.example.com" + @knife = Chef::Knife::DataBagCreate.new + @rest = ChefSpecs::ChefRest.new + @knife.stub!(:rest).and_return(@rest) + @stdout = StringIO.new + @knife.ui.stub!(:stdout).and_return(@stdout) + end + + + it "creates a data bag when given one argument" do + @knife.name_args = ['sudoing_admins'] + @rest.should_receive(:post_rest).with("data", {"name" => "sudoing_admins"}) + @knife.ui.should_receive(:info).with("Created data_bag[sudoing_admins]") + + @knife.run + end + + it "creates a data bag item when given two arguments" do + @knife.name_args = ['sudoing_admins', 'ME'] + user_supplied_hash = {"login_name" => "alphaomega", "id" => "ME"} + data_bag_item = Chef::DataBagItem.from_hash(user_supplied_hash) + data_bag_item.data_bag("sudoing_admins") + @knife.should_receive(:create_object).and_yield(user_supplied_hash) + @rest.should_receive(:post_rest).with("data", {'name' => 'sudoing_admins'}).ordered + @rest.should_receive(:post_rest).with("data/sudoing_admins", data_bag_item).ordered + + @knife.run + end + + describe "encrypted data bag items" do + before(:each) do + @secret = "abc123SECRET" + @plain_data = {"login_name" => "alphaomega", "id" => "ME"} + @enc_data = Chef::EncryptedDataBagItem.encrypt_data_bag_item(@plain_data, + @secret) + @knife.name_args = ['sudoing_admins', 'ME'] + @knife.should_receive(:create_object).and_yield(@plain_data) + data_bag_item = Chef::DataBagItem.from_hash(@enc_data) + data_bag_item.data_bag("sudoing_admins") + @rest.should_receive(:post_rest).with("data", {'name' => 'sudoing_admins'}).ordered + @rest.should_receive(:post_rest).with("data/sudoing_admins", data_bag_item).ordered + + @secret_file = Tempfile.new("encrypted_data_bag_secret_file_test") + @secret_file.puts(@secret) + @secret_file.flush + end + + after do + @secret_file.close + @secret_file.unlink + end + + it "creates an encrypted data bag item via --secret" do + @knife.stub!(:config).and_return({:secret => @secret}) + @knife.run + end + + it "creates an encrypted data bag item via --secret_file" do + secret_file = Tempfile.new("encrypted_data_bag_secret_file_test") + secret_file.puts(@secret) + secret_file.flush + @knife.stub!(:config).and_return({:secret_file => secret_file.path}) + @knife.run + end + end + +end diff --git a/spec/unit/knife/data_bag_edit_spec.rb b/spec/unit/knife/data_bag_edit_spec.rb new file mode 100644 index 0000000000..572722541a --- /dev/null +++ b/spec/unit/knife/data_bag_edit_spec.rb @@ -0,0 +1,89 @@ +# +# Author:: Seth Falcon (<seth@opscode.com>) +# Copyright:: Copyright 2010 Opscode, 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' +require 'tempfile' + +describe Chef::Knife::DataBagEdit do + before do + @plain_data = {"login_name" => "alphaomega", "id" => "item_name"} + @edited_data = { + "login_name" => "rho", "id" => "item_name", + "new_key" => "new_value" } + + Chef::Config[:node_name] = "webmonkey.example.com" + + @knife = Chef::Knife::DataBagEdit.new + @rest = mock('chef-rest-mock') + @knife.stub!(:rest).and_return(@rest) + + @stdout = StringIO.new + @knife.stub!(:stdout).and_return(@stdout) + @log = Chef::Log + @knife.name_args = ['bag_name', 'item_name'] + end + + it "requires data bag and item arguments" do + @knife.name_args = [] + lambda { @knife.run }.should raise_error(SystemExit) + @stdout.string.should match(/^You must supply the data bag and an item to edit/) + end + + it "saves edits on a data bag item" do + Chef::DataBagItem.stub!(:load).with('bag_name', 'item_name').and_return(@plain_data) + @knife.should_receive(:edit_data).with(@plain_data).and_return(@edited_data) + @rest.should_receive(:put_rest).with("data/bag_name/item_name", @edited_data).ordered + @knife.run + end + + describe "encrypted data bag items" do + before(:each) do + @secret = "abc123SECRET" + @enc_data = Chef::EncryptedDataBagItem.encrypt_data_bag_item(@plain_data, + @secret) + @enc_edited_data = Chef::EncryptedDataBagItem.encrypt_data_bag_item(@edited_data, + @secret) + Chef::DataBagItem.stub!(:load).with('bag_name', 'item_name').and_return(@enc_data) + + @secret_file = Tempfile.new("encrypted_data_bag_secret_file_test") + @secret_file.puts(@secret) + @secret_file.flush + end + + after do + @secret_file.close + @secret_file.unlink + end + + it "decrypts and encrypts via --secret" do + @knife.stub!(:config).and_return({:secret => @secret}) + @knife.should_receive(:edit_data).with(@plain_data).and_return(@edited_data) + @rest.should_receive(:put_rest).with("data/bag_name/item_name", @enc_edited_data).ordered + + @knife.run + end + + it "decrypts and encrypts via --secret_file" do + @knife.stub!(:config).and_return({:secret_file => @secret_file.path}) + @knife.should_receive(:edit_data).with(@plain_data).and_return(@edited_data) + @rest.should_receive(:put_rest).with("data/bag_name/item_name", @enc_edited_data).ordered + + @knife.run + end + end +end diff --git a/spec/unit/knife/data_bag_from_file_spec.rb b/spec/unit/knife/data_bag_from_file_spec.rb new file mode 100644 index 0000000000..f4ed7ca5de --- /dev/null +++ b/spec/unit/knife/data_bag_from_file_spec.rb @@ -0,0 +1,191 @@ +# +# Author:: Seth Falcon (<seth@opscode.com>) +# Copyright:: Copyright (c) 2010 Opscode, 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' + +require 'chef/data_bag_item' +require 'chef/encrypted_data_bag_item' +require 'tempfile' +require 'json' + +Chef::Knife::DataBagFromFile.load_deps + +describe Chef::Knife::DataBagFromFile do + before :each do + Chef::Config[:node_name] = "webmonkey.example.com" + @knife = Chef::Knife::DataBagFromFile.new + @rest = mock("Chef::REST") + @knife.stub!(:rest).and_return(@rest) + @stdout = StringIO.new + @knife.ui.stub!(:stdout).and_return(@stdout) + @tmp_dir = Dir.mktmpdir + @db_folder = File.join(@tmp_dir, 'data_bags', 'bag_name') + FileUtils.mkdir_p(@db_folder) + @db_file = Tempfile.new(["data_bag_from_file_test", ".json"], @db_folder) + @db_file2 = Tempfile.new(["data_bag_from_file_test2", ".json"], @db_folder) + @db_folder2 = File.join(@tmp_dir, 'data_bags', 'bag_name2') + FileUtils.mkdir_p(@db_folder2) + @db_file3 = Tempfile.new(["data_bag_from_file_test3", ".json"], @db_folder2) + @plain_data = { + "id" => "item_name", + "greeting" => "hello", + "nested" => { "a1" => [1, 2, 3], "a2" => { "b1" => true }} + } + @db_file.write(@plain_data.to_json) + @db_file.flush + @knife.instance_variable_set(:@name_args, ['bag_name', @db_file.path]) + end + + # We have to explicitly clean up Tempfile on Windows because it said so. + after :each do + @db_file.close + @db_file2.close + @db_file3.close + FileUtils.rm_rf(@db_folder) + FileUtils.rm_rf(@db_folder2) + FileUtils.remove_entry_secure @tmp_dir + end + + it "loads from a file and saves" do + @knife.loader.should_receive(:load_from).with("data_bags", 'bag_name', @db_file.path).and_return(@plain_data) + dbag = Chef::DataBagItem.new + Chef::DataBagItem.stub!(:new).and_return(dbag) + dbag.should_receive(:save) + @knife.run + + dbag.data_bag.should == 'bag_name' + dbag.raw_data.should == @plain_data + end + + it "loads all from a mutiple files and saves" do + @knife.name_args = [ 'bag_name', @db_file.path, @db_file2.path ] + @knife.loader.should_receive(:load_from).with("data_bags", 'bag_name', @db_file.path).and_return(@plain_data) + @knife.loader.should_receive(:load_from).with("data_bags", 'bag_name', @db_file2.path).and_return(@plain_data) + dbag = Chef::DataBagItem.new + Chef::DataBagItem.stub!(:new).and_return(dbag) + dbag.should_receive(:save).twice + @knife.run + + dbag.data_bag.should == 'bag_name' + dbag.raw_data.should == @plain_data + end + + it "loads all from a folder and saves" do + dir = File.dirname(@db_file.path) + @knife.name_args = [ 'bag_name', @db_folder ] + @knife.loader.should_receive(:load_from).with("data_bags", 'bag_name', @db_file.path).and_return(@plain_data) + @knife.loader.should_receive(:load_from).with("data_bags", 'bag_name', @db_file2.path).and_return(@plain_data) + dbag = Chef::DataBagItem.new + Chef::DataBagItem.stub!(:new).and_return(dbag) + dbag.should_receive(:save).twice + @knife.run + end + + describe "loading all data bags" do + + before do + @pwd = Dir.pwd + Dir.chdir(@tmp_dir) + end + + after do + Dir.chdir(@pwd) + end + + it "loads all data bags when -a or --all options is provided" do + @knife.name_args = [] + @knife.stub!(:config).and_return({:all => true}) + @knife.loader.should_receive(:load_from).with("data_bags", "bag_name", File.basename(@db_file.path)). + and_return(@plain_data) + @knife.loader.should_receive(:load_from).with("data_bags", "bag_name", File.basename(@db_file2.path)). + and_return(@plain_data) + @knife.loader.should_receive(:load_from).with("data_bags", "bag_name2", File.basename(@db_file3.path)). + and_return(@plain_data) + dbag = Chef::DataBagItem.new + Chef::DataBagItem.stub!(:new).and_return(dbag) + dbag.should_receive(:save).exactly(3).times + @knife.run + end + + it "loads all data bags items when -a or --all options is provided" do + @knife.name_args = ["bag_name2"] + @knife.stub!(:config).and_return({:all => true}) + @knife.loader.should_receive(:load_from).with("data_bags", "bag_name2", File.basename(@db_file3.path)). + and_return(@plain_data) + dbag = Chef::DataBagItem.new + Chef::DataBagItem.stub!(:new).and_return(dbag) + dbag.should_receive(:save) + @knife.run + dbag.data_bag.should == 'bag_name2' + dbag.raw_data.should == @plain_data + end + + end + + describe "encrypted data bag items" do + before(:each) do + @secret = "abc123SECRET" + @enc_data = Chef::EncryptedDataBagItem.encrypt_data_bag_item(@plain_data, + @secret) + @secret_file = Tempfile.new("encrypted_data_bag_secret_file_test") + @secret_file.puts(@secret) + @secret_file.flush + end + + after do + @secret_file.close + @secret_file.unlink + end + + it "encrypts values when given --secret" do + @knife.stub!(:config).and_return({:secret => @secret}) + + @knife.loader.should_receive(:load_from).with("data_bags", "bag_name", @db_file.path).and_return(@plain_data) + dbag = Chef::DataBagItem.new + Chef::DataBagItem.stub!(:new).and_return(dbag) + dbag.should_receive(:save) + @knife.run + dbag.data_bag.should == 'bag_name' + dbag.raw_data.should == @enc_data + end + + it "encrypts values when given --secret_file" do + @knife.stub!(:config).and_return({:secret_file => @secret_file.path}) + + @knife.loader.stub!(:load_from).with("data_bags", 'bag_name', @db_file.path).and_return(@plain_data) + dbag = Chef::DataBagItem.new + Chef::DataBagItem.stub!(:new).and_return(dbag) + dbag.should_receive(:save) + @knife.run + dbag.data_bag.should == 'bag_name' + dbag.raw_data.should == @enc_data + end + + end + + describe "command line parsing" do + it "prints help if given no arguments" do + @knife.instance_variable_set(:@name_args, []) + lambda { @knife.run }.should raise_error(SystemExit) + help_text = "knife data bag from file BAG FILE|FOLDER [FILE|FOLDER..] (options)" + help_text_regex = Regexp.new("^#{Regexp.escape(help_text)}") + @stdout.string.should match(help_text_regex) + end + end + +end diff --git a/spec/unit/knife/data_bag_show_spec.rb b/spec/unit/knife/data_bag_show_spec.rb new file mode 100644 index 0000000000..08ecfaa0a7 --- /dev/null +++ b/spec/unit/knife/data_bag_show_spec.rb @@ -0,0 +1,112 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Author:: Seth Falcon (<seth@opscode.com>) +# Copyright:: Copyright (c) 2008-2010 Opscode, 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' + +require 'chef/data_bag_item' +require 'chef/encrypted_data_bag_item' +require 'chef/json_compat' +require 'tempfile' + +describe Chef::Knife::DataBagShow do + before do + Chef::Config[:node_name] = "webmonkey.example.com" + @knife = Chef::Knife::DataBagShow.new + @knife.config[:format] = 'json' + @rest = mock("Chef::REST") + @knife.stub!(:rest).and_return(@rest) + @stdout = StringIO.new + @knife.ui.stub!(:stdout).and_return(@stdout) + end + + + it "prints the ids of the data bag items when given a bag name" do + @knife.instance_variable_set(:@name_args, ['bag_o_data']) + data_bag_contents = { "baz"=>"http://localhost:4000/data/bag_o_data/baz", + "qux"=>"http://localhost:4000/data/bag_o_data/qux"} + Chef::DataBag.should_receive(:load).and_return(data_bag_contents) + expected = %q|[ + "baz", + "qux" +]| + @knife.run + @stdout.string.strip.should == expected + end + + it "prints the contents of the data bag item when given a bag and item name" do + @knife.instance_variable_set(:@name_args, ['bag_o_data', 'an_item']) + data_item = Chef::DataBagItem.new.tap {|item| item.raw_data = {"id" => "an_item", "zsh" => "victory_through_tabbing"}} + + Chef::DataBagItem.should_receive(:load).with('bag_o_data', 'an_item').and_return(data_item) + + @knife.run + Chef::JSONCompat.from_json(@stdout.string).should == data_item.raw_data + + end + + describe "encrypted data bag items" do + before(:each) do + @secret = "abc123SECRET" + @plain_data = { + "id" => "item_name", + "greeting" => "hello", + "nested" => { "a1" => [1, 2, 3], "a2" => { "b1" => true }} + } + @enc_data = Chef::EncryptedDataBagItem.encrypt_data_bag_item(@plain_data, + @secret) + @knife.instance_variable_set(:@name_args, ['bag_name', 'item_name']) + + @secret_file = Tempfile.new("encrypted_data_bag_secret_file_test") + @secret_file.puts(@secret) + @secret_file.flush + end + + after do + @secret_file.close + @secret_file.unlink + end + + it "prints the decrypted contents of an item when given --secret" do + @knife.stub!(:config).and_return({:secret => @secret}) + Chef::EncryptedDataBagItem.should_receive(:load). + with('bag_name', 'item_name', @secret). + and_return(Chef::EncryptedDataBagItem.new(@enc_data, @secret)) + @knife.run + Chef::JSONCompat.from_json(@stdout.string).should == @plain_data + end + + it "prints the decrypted contents of an item when given --secret_file" do + @knife.stub!(:config).and_return({:secret_file => @secret_file.path}) + Chef::EncryptedDataBagItem.should_receive(:load). + with('bag_name', 'item_name', @secret). + and_return(Chef::EncryptedDataBagItem.new(@enc_data, @secret)) + @knife.run + Chef::JSONCompat.from_json(@stdout.string).should == @plain_data + end + end + + describe "command line parsing" do + it "prints help if given no arguments" do + @knife.instance_variable_set(:@name_args, []) + lambda { @knife.run }.should raise_error(SystemExit) + @stdout.string.should match(/^knife data bag show BAG \[ITEM\] \(options\)/) + end + end + +end diff --git a/spec/unit/knife/environment_create_spec.rb b/spec/unit/knife/environment_create_spec.rb new file mode 100644 index 0000000000..36f6556682 --- /dev/null +++ b/spec/unit/knife/environment_create_spec.rb @@ -0,0 +1,91 @@ +# +# Author:: Stephen Delano (<stephen@ospcode.com>) +# Copyright:: Copyright (c) 2010 Opscode, 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::EnvironmentCreate do + before(:each) do + @knife = Chef::Knife::EnvironmentCreate.new + @knife.stub!(:msg).and_return true + @knife.stub!(:output).and_return true + @knife.stub!(:show_usage).and_return true + @knife.name_args = [ "production" ] + + @environment = Chef::Environment.new + @environment.stub!(:save) + + Chef::Environment.stub!(:new).and_return @environment + @knife.stub!(:edit_data).and_return @environment + end + + describe "run" do + it "should create a new environment" do + Chef::Environment.should_receive(:new) + @knife.run + end + + it "should set the environment name" do + @environment.should_receive(:name).with("production") + @knife.run + end + + it "should not print the environment" do + @knife.should_not_receive(:output) + @knife.run + end + + it "should prompt you to edit the data" do + @knife.should_receive(:edit_data).with(@environment) + @knife.run + end + + it "should save the environment" do + @environment.should_receive(:save) + @knife.run + end + + it "should show usage and exit when no environment name is provided" do + @knife.name_args = [ ] + @knife.ui.should_receive(:fatal) + @knife.should_receive(:show_usage) + lambda { @knife.run }.should raise_error(SystemExit) + end + + describe "with --description" do + before(:each) do + @knife.config[:description] = "This is production" + end + + it "should set the description" do + @environment.should_receive(:description).with("This is production") + @knife.run + end + end + + describe "with --print-after" do + before(:each) do + @knife.config[:print_after] = true + end + + it "should pretty print the environment, formatted for display" do + @knife.should_receive(:output).with(@environment) + @knife.run + end + end + end +end diff --git a/spec/unit/knife/environment_delete_spec.rb b/spec/unit/knife/environment_delete_spec.rb new file mode 100644 index 0000000000..219ae4a923 --- /dev/null +++ b/spec/unit/knife/environment_delete_spec.rb @@ -0,0 +1,71 @@ +# +# Author:: Stephen Delano (<stephen@ospcode.com>) +# Copyright:: Copyright (c) 2010 Opscode, 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::EnvironmentDelete do + before(:each) do + @knife = Chef::Knife::EnvironmentDelete.new + @knife.stub!(:msg).and_return true + @knife.stub!(:output).and_return true + @knife.stub!(:show_usage).and_return true + @knife.stub!(:confirm).and_return true + @knife.name_args = [ "production" ] + + @environment = Chef::Environment.new + @environment.name("production") + @environment.description("Please delete me") + @environment.stub!(:destroy).and_return true + Chef::Environment.stub!(:load).and_return @environment + end + + it "should confirm that you want to delete" do + @knife.should_receive(:confirm) + @knife.run + end + + it "should load the environment" do + Chef::Environment.should_receive(:load).with("production") + @knife.run + end + + it "should delete the environment" do + @environment.should_receive(:destroy) + @knife.run + end + + it "should not print the environment" do + @knife.should_not_receive(:output) + @knife.run + end + + it "should show usage and exit when no environment name is provided" do + @knife.name_args = [] + @knife.ui.should_receive(:fatal) + @knife.should_receive(:show_usage) + lambda { @knife.run }.should raise_error(SystemExit) + end + + describe "with --print-after" do + it "should pretty print the environment, formatted for display" do + @knife.config[:print_after] = true + @knife.should_receive(:output).with(@environment) + @knife.run + end + end +end diff --git a/spec/unit/knife/environment_edit_spec.rb b/spec/unit/knife/environment_edit_spec.rb new file mode 100644 index 0000000000..91f9f5d0f0 --- /dev/null +++ b/spec/unit/knife/environment_edit_spec.rb @@ -0,0 +1,79 @@ +# +# Author:: Stephen Delano (<stephen@ospcode.com>) +# Copyright:: Copyright (c) 2010 Opscode, 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::EnvironmentEdit do + before(:each) do + @knife = Chef::Knife::EnvironmentEdit.new + @knife.ui.stub!(:msg).and_return true + @knife.ui.stub!(:output).and_return true + @knife.ui.stub!(:show_usage).and_return true + @knife.name_args = [ "production" ] + + @environment = Chef::Environment.new + @environment.name("production") + @environment.description("Please edit me") + @environment.stub!(:save).and_return true + Chef::Environment.stub!(:load).and_return @environment + @knife.ui.stub(:edit_data).and_return @environment + end + + it "should load the environment" do + Chef::Environment.should_receive(:load).with("production") + @knife.run + end + + it "should let you edit the environment" do + @knife.ui.should_receive(:edit_data).with(@environment) + @knife.run + end + + it "should save the edited environment data" do + pansy = Chef::Environment.new + + @environment.name("new_environment_name") + @knife.ui.should_receive(:edit_data).with(@environment).and_return(pansy) + pansy.should_receive(:save) + @knife.run + end + + it "should not save the unedited environment data" do + @environment.should_not_receive(:save) + @knife.run + end + + it "should not print the environment" do + @knife.should_not_receive(:output) + @knife.run + end + + it "shoud show usage and exit when no environment name is provided" do + @knife.name_args = [] + @knife.should_receive(:show_usage) + lambda { @knife.run }.should raise_error(SystemExit) + end + + describe "with --print-after" do + it "should pretty print the environment, formatted for display" do + @knife.config[:print_after] = true + @knife.ui.should_receive(:output).with(@environment) + @knife.run + end + end +end diff --git a/spec/unit/knife/environment_from_file_spec.rb b/spec/unit/knife/environment_from_file_spec.rb new file mode 100644 index 0000000000..d2234d9be1 --- /dev/null +++ b/spec/unit/knife/environment_from_file_spec.rb @@ -0,0 +1,89 @@ +# +# Author:: Stephen Delano (<stephen@ospcode.com>) +# Author:: Seth Falcon (<seth@ospcode.com>) +# Copyright:: Copyright 2010 Opscode, 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' + +Chef::Knife::EnvironmentFromFile.load_deps + +describe Chef::Knife::EnvironmentFromFile do + before(:each) do + @knife = Chef::Knife::EnvironmentFromFile.new + @stdout = StringIO.new + @knife.ui.stub!(:stdout).and_return(@stdout) + @knife.name_args = [ "spec.rb" ] + + @environment = Chef::Environment.new + @environment.name("spec") + @environment.description("runs the unit tests") + @environment.cookbook_versions({"apt" => "= 1.2.3"}) + @environment.stub!(:save).and_return true + @knife.loader.stub!(:load_from).and_return @environment + end + + describe "run" do + it "loads the environment data from a file and saves it" do + @knife.loader.should_receive(:load_from).with('environments', 'spec.rb').and_return(@environment) + @environment.should_receive(:save) + @knife.run + end + + context "when handling multiple environments" do + before(:each) do + @env_apple = @environment.dup + @env_apple.name("apple") + @knife.loader.stub!(:load_from).with("apple.rb").and_return @env_apple + end + + it "loads multiple environments if given" do + @knife.name_args = [ "spec.rb", "apple.rb" ] + @environment.should_receive(:save).twice + @knife.run + end + + it "loads all environments with -a" do + File.stub!(:expand_path).with("./environments/*.{json,rb}").and_return("/tmp/environments") + Dir.stub!(:glob).with("/tmp/environments").and_return(["spec.rb", "apple.rb"]) + @knife.name_args = [] + @knife.stub!(:config).and_return({:all => true}) + @environment.should_receive(:save).twice + @knife.run + end + end + + it "should not print the environment" do + @knife.should_not_receive(:output) + @knife.run + end + + it "should show usage and exit if not filename is provided" do + @knife.name_args = [] + @knife.ui.should_receive(:fatal) + @knife.should_receive(:show_usage) + lambda { @knife.run }.should raise_error(SystemExit) + end + + describe "with --print-after" do + it "should pretty print the environment, formatted for display" do + @knife.config[:print_after] = true + @knife.should_receive(:output) + @knife.run + end + end + end +end diff --git a/spec/unit/knife/environment_list_spec.rb b/spec/unit/knife/environment_list_spec.rb new file mode 100644 index 0000000000..05a3ae748a --- /dev/null +++ b/spec/unit/knife/environment_list_spec.rb @@ -0,0 +1,54 @@ +# +# Author:: Stephen Delano (<stephen@ospcode.com>) +# Copyright:: Copyright (c) 2010 Opscode, 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::EnvironmentList do + before(:each) do + @knife = Chef::Knife::EnvironmentList.new + @knife.stub!(:msg).and_return true + @knife.stub!(:output).and_return true + @knife.stub!(:show_usage).and_return true + + @environments = { + "production" => "http://localhost:4000/environments/production", + "development" => "http://localhost:4000/environments/development", + "testing" => "http://localhost:4000/environments/testing" + } + Chef::Environment.stub!(:list).and_return @environments + end + + it "should make an api call to list the environments" do + Chef::Environment.should_receive(:list) + @knife.run + end + + it "should print the environment names in a sorted list" do + names = @environments.keys.sort { |a,b| a <=> b } + @knife.should_receive(:output).with(names) + @knife.run + end + + describe "with --with-uri" do + it "should print and unsorted list of the environments and their URIs" do + @knife.config[:with_uri] = true + @knife.should_receive(:output).with(@environments) + @knife.run + end + end +end diff --git a/spec/unit/knife/environment_show_spec.rb b/spec/unit/knife/environment_show_spec.rb new file mode 100644 index 0000000000..1e1556f4c3 --- /dev/null +++ b/spec/unit/knife/environment_show_spec.rb @@ -0,0 +1,52 @@ +# +# Author:: Stephen Delano (<stephen@ospcode.com>) +# Copyright:: Copyright (c) 2010 Opscode, 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::EnvironmentShow do + before(:each) do + @knife = Chef::Knife::EnvironmentShow.new + @knife.stub!(:msg).and_return true + @knife.stub!(:output).and_return true + @knife.stub!(:show_usage).and_return true + @knife.name_args = [ "production" ] + + @environment = Chef::Environment.new + @environment.name("production") + @environment.description("Look at me!") + Chef::Environment.stub!(:load).and_return @environment + end + + it "should load the environment" do + Chef::Environment.should_receive(:load).with("production") + @knife.run + end + + it "should pretty print the environment, formatted for display" do + @knife.should_receive(:format_for_display).with(@environment) + @knife.should_receive(:output) + @knife.run + end + + it "should show usage and exit when no environment name is provided" do + @knife.name_args = [] + @knife.ui.should_receive(:fatal) + @knife.should_receive(:show_usage) + lambda { @knife.run }.should raise_error(SystemExit) + end +end diff --git a/spec/unit/knife/index_rebuild_spec.rb b/spec/unit/knife/index_rebuild_spec.rb new file mode 100644 index 0000000000..3b22a3b88c --- /dev/null +++ b/spec/unit/knife/index_rebuild_spec.rb @@ -0,0 +1,65 @@ +# +# Author:: Daniel DeLeo (<dan@kallistec.com>) +# Copyright:: Copyright (c) 2009 Daniel DeLeo +# 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::IndexRebuild do + before do + Chef::Config[:node_name] = "webmonkey.example.com" + @knife = Chef::Knife::IndexRebuild.new + @rest_client = mock("Chef::REST (mock)", :post_rest => { :result => :true }) + @knife.ui.stub!(:output) + @knife.stub!(:rest).and_return(@rest_client) + + @out = StringIO.new + @knife.ui.stub!(:stdout).and_return(@out) + end + + it "asks a yes/no confirmation and aborts on 'no'" do + @knife.ui.stub!(:stdin).and_return(StringIO.new("NO\n")) + @knife.should_receive(:puts) + @knife.should_receive(:exit).with(7) + @knife.run + @out.string.should match(/yes\/no/) + end + + it "asks a confirmation and continues on 'yes'" do + @knife.ui.stub!(:stdin).and_return(StringIO.new("yes\n")) + @knife.should_not_receive(:exit) + @knife.run + @out.string.should match(/yes\/no/) + end + + describe "after confirming the operation" do + before do + @knife.ui.stub!(:print) + @knife.ui.stub!(:puts) + @knife.stub!(:nag) + @knife.ui.stub!(:output) + end + + it "POSTs to /search/reindex and displays the result" do + @rest_client = mock("Chef::REST") + @knife.stub!(:rest).and_return(@rest_client) + @rest_client.should_receive(:post_rest).with("/search/reindex", {}).and_return("monkey") + @knife.should_receive(:output).with("monkey") + @knife.run + end + end + +end diff --git a/spec/unit/knife/knife_help.rb b/spec/unit/knife/knife_help.rb new file mode 100644 index 0000000000..f5753e3d62 --- /dev/null +++ b/spec/unit/knife/knife_help.rb @@ -0,0 +1,92 @@ +# +# Author:: Bryan McLellan <btm@loftninjas.org> +# Copyright:: Copyright (c) 2011 Opscode, 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::Help do + before(:each) do + # Perilously use the build in list even though it is dynamic so we don't get warnings about the constant + # HELP_TOPICS = [ "foo", "bar", "knife-kittens", "ceiling-cat", "shell" ] + @knife = Chef::Knife::Help.new + end + + it "should return a list of help topics" do + @knife.help_topics.should include("knife-status") + end + + it "should run man for you" do + @knife.name_args = [ "shell" ] + @knife.should_receive(:exec).with(/^man \/.*\/shell.1$/) + @knife.run + end + + it "should suggest topics" do + @knife.name_args = [ "list" ] + @knife.ui.stub!(:msg) + @knife.ui.should_receive(:info).with("Available help topics are: ") + @knife.ui.should_receive(:msg).with(/knife/) + @knife.stub!(:exec) + @knife.should_receive(:exit).with(1) + @knife.run + end + + describe "find_manpage_path" do + it "should find the man page in the gem" do + @knife.find_manpage_path("shell").should =~ /distro\/common\/man\/man1\/chef-shell.1$/ + end + + it "should provide the man page name if not in the gem" do + @knife.find_manpage_path("foo").should == "foo" + end + end + + describe "find_manpages_for_query" do + it "should error if it does not find a match" do + @knife.ui.stub!(:error) + @knife.ui.stub!(:info) + @knife.ui.stub!(:msg) + @knife.should_receive(:exit).with(1) + @knife.ui.should_receive(:error).with("No help found for 'chickens'") + @knife.ui.should_receive(:msg).with(/knife/) + @knife.find_manpages_for_query("chickens") + end + end + + describe "print_help_topics" do + it "should print the known help topics" do + @knife.ui.stub!(:msg) + @knife.ui.stub!(:info) + @knife.ui.should_receive(:msg).with(/knife/) + @knife.print_help_topics + end + + it "should shorten topics prefixed by knife-" do + @knife.ui.stub!(:msg) + @knife.ui.stub!(:info) + @knife.ui.should_receive(:msg).with(/node/) + @knife.print_help_topics + end + + it "should not leave topics prefixed by knife-" do + @knife.ui.stub!(:msg) + @knife.ui.stub!(:info) + @knife.ui.should_not_receive(:msg).with(/knife-node/) + @knife.print_help_topics + end + end +end diff --git a/spec/unit/knife/node_bulk_delete_spec.rb b/spec/unit/knife/node_bulk_delete_spec.rb new file mode 100644 index 0000000000..51f707dfcf --- /dev/null +++ b/spec/unit/knife/node_bulk_delete_spec.rb @@ -0,0 +1,97 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Copyright:: Copyright (c) 2008 Opscode, 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::NodeBulkDelete do + before(:each) do + Chef::Log.logger = Logger.new(StringIO.new) + + Chef::Config[:node_name] = "webmonkey.example.com" + @knife = Chef::Knife::NodeBulkDelete.new + @knife.name_args = ["."] + @stdout = StringIO.new + @knife.ui.stub!(:stdout).and_return(@stdout) + @knife.ui.stub!(:confirm).and_return(true) + @nodes = Hash.new + %w{adam brent jacob}.each do |node_name| + @nodes[node_name] = "http://localhost:4000/nodes/#{node_name}" + end + end + + describe "when creating the list of nodes" do + it "fetches the node list" do + expected = @nodes.inject({}) do |inflatedish, (name, uri)| + inflatedish[name] = Chef::Node.new.tap {|n| n.name(name)} + inflatedish + end + Chef::Node.should_receive(:list).and_return(@nodes) + # I hate not having == defined for anything :( + actual = @knife.all_nodes + actual.keys.should =~ expected.keys + actual.values.map {|n| n.name }.should =~ %w[adam brent jacob] + end + end + + describe "run" do + before do + @inflatedish_list = @nodes.keys.inject({}) do |nodes_by_name, name| + node = Chef::Node.new() + node.name(name) + node.stub!(:destroy).and_return(true) + nodes_by_name[name] = node + nodes_by_name + end + @knife.stub!(:all_nodes).and_return(@inflatedish_list) + end + + it "should print the nodes you are about to delete" do + @knife.run + @stdout.string.should match(/#{@knife.ui.list(@nodes.keys.sort, :columns_down)}/) + end + + it "should confirm you really want to delete them" do + @knife.ui.should_receive(:confirm) + @knife.run + end + + it "should delete each node" do + @inflatedish_list.each_value do |n| + n.should_receive(:destroy) + end + @knife.run + end + + it "should only delete nodes that match the regex" do + @knife.name_args = ['adam'] + @inflatedish_list['adam'].should_receive(:destroy) + @inflatedish_list['brent'].should_not_receive(:destroy) + @inflatedish_list['jacob'].should_not_receive(:destroy) + @knife.run + end + + it "should exit if the regex is not provided" do + @knife.name_args = [] + lambda { @knife.run }.should raise_error(SystemExit) + end + + end +end + + + diff --git a/spec/unit/knife/node_delete_spec.rb b/spec/unit/knife/node_delete_spec.rb new file mode 100644 index 0000000000..b1b3db1aa4 --- /dev/null +++ b/spec/unit/knife/node_delete_spec.rb @@ -0,0 +1,68 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Copyright:: Copyright (c) 2008 Opscode, 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::NodeDelete do + before(:each) do + Chef::Config[:node_name] = "webmonkey.example.com" + @knife = Chef::Knife::NodeDelete.new + @knife.config = { + :print_after => nil + } + @knife.name_args = [ "adam" ] + @knife.stub!(:output).and_return(true) + @knife.stub!(:confirm).and_return(true) + @node = Chef::Node.new() + @node.stub!(:destroy).and_return(true) + Chef::Node.stub!(:load).and_return(@node) + @stdout = StringIO.new + @knife.ui.stub!(:stdout).and_return(@stdout) + end + + describe "run" do + it "should confirm that you want to delete" do + @knife.should_receive(:confirm) + @knife.run + end + + it "should load the node" do + Chef::Node.should_receive(:load).with("adam").and_return(@node) + @knife.run + end + + it "should delete the node" do + @node.should_receive(:destroy).and_return(@node) + @knife.run + end + + it "should not print the node" do + @knife.should_not_receive(:output).with("poop") + @knife.run + end + + describe "with -p or --print-after" do + it "should pretty print the node, formatted for display" do + @knife.config[:print_after] = true + @knife.should_receive(:format_for_display).with(@node).and_return("poop") + @knife.should_receive(:output).with("poop") + @knife.run + end + end + end +end diff --git a/spec/unit/knife/node_edit_spec.rb b/spec/unit/knife/node_edit_spec.rb new file mode 100644 index 0000000000..0ba2e90cfe --- /dev/null +++ b/spec/unit/knife/node_edit_spec.rb @@ -0,0 +1,88 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Copyright:: Copyright (c) 2008 Opscode, 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' +Chef::Knife::NodeEdit.load_deps + +describe Chef::Knife::NodeEdit do + before(:each) do + Chef::Config[:node_name] = "webmonkey.example.com" + @knife = Chef::Knife::NodeEdit.new + @knife.config = { + :editor => 'cat', + :attribute => nil, + :print_after => nil + } + @knife.name_args = [ "adam" ] + @node = Chef::Node.new() + end + + it "should load the node" do + Chef::Node.should_receive(:load).with("adam").and_return(@node) + @knife.node + end + + describe "after loading the node" do + before do + @knife.stub!(:node).and_return(@node) + @node.automatic_attrs = {:go => :away} + @node.default_attrs = {:hide => :me} + @node.override_attrs = {:dont => :show} + @node.normal_attrs = {:do_show => :these} + @node.chef_environment("prod") + @node.run_list("recipe[foo]") + end + + it "creates a view of the node without attributes from roles or ohai" do + actual = Chef::JSONCompat.from_json(@knife.node_editor.view) + actual.should_not have_key("automatic") + actual.should_not have_key("override") + actual.should_not have_key("default") + actual["normal"].should == {"do_show" => "these"} + actual["run_list"].should == ["recipe[foo]"] + actual["chef_environment"].should == "prod" + end + + it "shows the extra attributes when given the --all option" do + @knife.config[:all_attributes] = true + + actual = Chef::JSONCompat.from_json(@knife.node_editor.view) + actual["automatic"].should == {"go" => "away"} + actual["override"].should == {"dont" => "show"} + actual["default"].should == {"hide" => "me"} + actual["normal"].should == {"do_show" => "these"} + actual["run_list"].should == ["recipe[foo]"] + actual["chef_environment"].should == "prod" + end + + it "does not consider unedited data updated" do + view = Chef::JSONCompat.from_json( @knife.node_editor.view ) + @knife.node_editor.apply_updates(view) + @knife.node_editor.should_not be_updated + end + + it "considers edited data updated" do + view = Chef::JSONCompat.from_json( @knife.node_editor.view ) + view["run_list"] << "role[fuuu]" + @knife.node_editor.apply_updates(view) + @knife.node_editor.should be_updated + end + + end +end + diff --git a/spec/unit/knife/node_from_file_spec.rb b/spec/unit/knife/node_from_file_spec.rb new file mode 100644 index 0000000000..c6b9610d9e --- /dev/null +++ b/spec/unit/knife/node_from_file_spec.rb @@ -0,0 +1,59 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Copyright:: Copyright (c) 2008 Opscode, 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' + +Chef::Knife::NodeFromFile.load_deps + +describe Chef::Knife::NodeFromFile do + before(:each) do + Chef::Config[:node_name] = "webmonkey.example.com" + @knife = Chef::Knife::NodeFromFile.new + @knife.config = { + :print_after => nil + } + @knife.name_args = [ "adam.rb" ] + @knife.stub!(:output).and_return(true) + @knife.stub!(:confirm).and_return(true) + @node = Chef::Node.new() + @node.stub!(:save) + @knife.loader.stub!(:load_from).and_return(@node) + @stdout = StringIO.new + @knife.ui.stub!(:stdout).and_return(@stdout) + end + + describe "run" do + it "should load from a file" do + @knife.loader.should_receive(:load_from).with('nodes', 'adam.rb').and_return(@node) + @knife.run + end + + it "should not print the Node" do + @knife.should_not_receive(:output) + @knife.run + end + + describe "with -p or --print-after" do + it "should print the Node" do + @knife.config[:print_after] = true + @knife.should_receive(:output) + @knife.run + end + end + end +end diff --git a/spec/unit/knife/node_list_spec.rb b/spec/unit/knife/node_list_spec.rb new file mode 100644 index 0000000000..5637d679c8 --- /dev/null +++ b/spec/unit/knife/node_list_spec.rb @@ -0,0 +1,63 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Copyright:: Copyright (c) 2008 Opscode, 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::NodeList do + before(:each) do + Chef::Config[:node_name] = "webmonkey.example.com" + Chef::Config[:environment] = nil # reset this value each time, as it is not reloaded + @knife = Chef::Knife::NodeList.new + @knife.stub!(:output).and_return(true) + @list = { + "foo" => "http://example.com/foo", + "bar" => "http://example.com/foo" + } + Chef::Node.stub!(:list).and_return(@list) + Chef::Node.stub!(:list_by_environment).and_return(@list) + end + + describe "run" do + it "should list all of the nodes if -E is not specified" do + Chef::Node.should_receive(:list).and_return(@list) + @knife.run + end + + it "should pretty print the list" do + Chef::Node.should_receive(:list).and_return(@list) + @knife.should_receive(:output).with([ "bar", "foo" ]) + @knife.run + end + + it "should list nodes in the specific environment if -E ENVIRONMENT is specified" do + Chef::Config[:environment] = "prod" + Chef::Node.should_receive(:list_by_environment).with("prod").and_return(@list) + @knife.run + end + + describe "with -w or --with-uri" do + it "should pretty print the hash" do + @knife.config[:with_uri] = true + Chef::Node.should_receive(:list).and_return(@list) + @knife.should_receive(:output).with(@list) + @knife.run + end + end + end +end + diff --git a/spec/unit/knife/node_run_list_add_spec.rb b/spec/unit/knife/node_run_list_add_spec.rb new file mode 100644 index 0000000000..ee0cfc9038 --- /dev/null +++ b/spec/unit/knife/node_run_list_add_spec.rb @@ -0,0 +1,125 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Copyright:: Copyright (c) 2008 Opscode, 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::NodeRunListAdd do + before(:each) do + Chef::Config[:node_name] = "webmonkey.example.com" + @knife = Chef::Knife::NodeRunListAdd.new + @knife.config = { + :after => nil + } + @knife.name_args = [ "adam", "role[monkey]" ] + @knife.stub!(:output).and_return(true) + @node = Chef::Node.new() + @node.stub!(:save).and_return(true) + Chef::Node.stub!(:load).and_return(@node) + end + + describe "run" do + it "should load the node" do + Chef::Node.should_receive(:load).with("adam") + @knife.run + end + + it "should add to the run list" do + @knife.run + @node.run_list[0].should == 'role[monkey]' + end + + it "should save the node" do + @node.should_receive(:save) + @knife.run + end + + it "should print the run list" do + @knife.should_receive(:output).and_return(true) + @knife.run + end + + describe "with -a or --after specified" do + it "should add to the run list after the specified entry" do + @node.run_list << "role[acorns]" + @node.run_list << "role[barn]" + @knife.config[:after] = "role[acorns]" + @knife.run + @node.run_list[0].should == "role[acorns]" + @node.run_list[1].should == "role[monkey]" + @node.run_list[2].should == "role[barn]" + end + end + + describe "with more than one role or recipe" do + it "should add to the run list all the entries" do + @knife.name_args = [ "adam", "role[monkey],role[duck]" ] + @node.run_list << "role[acorns]" + @knife.run + @node.run_list[0].should == "role[acorns]" + @node.run_list[1].should == "role[monkey]" + @node.run_list[2].should == "role[duck]" + end + end + + describe "with more than one role or recipe with space between items" do + it "should add to the run list all the entries" do + @knife.name_args = [ "adam", "role[monkey], role[duck]" ] + @node.run_list << "role[acorns]" + @knife.run + @node.run_list[0].should == "role[acorns]" + @node.run_list[1].should == "role[monkey]" + @node.run_list[2].should == "role[duck]" + end + end + + describe "with more than one role or recipe as different arguments" do + it "should add to the run list all the entries" do + @knife.name_args = [ "adam", "role[monkey]", "role[duck]" ] + @node.run_list << "role[acorns]" + @knife.run + @node.run_list[0].should == "role[acorns]" + @node.run_list[1].should == "role[monkey]" + @node.run_list[2].should == "role[duck]" + end + end + + describe "with more than one role or recipe as different arguments and list separated by comas" do + it "should add to the run list all the entries" do + @knife.name_args = [ "adam", "role[monkey]", "role[duck],recipe[bird::fly]" ] + @node.run_list << "role[acorns]" + @knife.run + @node.run_list[0].should == "role[acorns]" + @node.run_list[1].should == "role[monkey]" + @node.run_list[2].should == "role[duck]" + end + end + + describe "with one role or recipe but with an extraneous comma" do + it "should add to the run list one item" do + @knife.name_args = [ "adam", "role[monkey]," ] + @node.run_list << "role[acorns]" + @knife.run + @node.run_list[0].should == "role[acorns]" + @node.run_list[1].should == "role[monkey]" + end + end + end +end + + + diff --git a/spec/unit/knife/node_run_list_remove_spec.rb b/spec/unit/knife/node_run_list_remove_spec.rb new file mode 100644 index 0000000000..90869e8baa --- /dev/null +++ b/spec/unit/knife/node_run_list_remove_spec.rb @@ -0,0 +1,74 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Copyright:: Copyright (c) 2008 Opscode, 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::NodeRunListRemove do + before(:each) do + Chef::Config[:node_name] = "webmonkey.example.com" + @knife = Chef::Knife::NodeRunListRemove.new + @knife.config[:print_after] = nil + @knife.name_args = [ "adam", "role[monkey]" ] + @node = Chef::Node.new() + @node.name("knifetest-node") + @node.run_list << "role[monkey]" + @node.stub!(:save).and_return(true) + + @knife.ui.stub!(:output).and_return(true) + @knife.ui.stub!(:confirm).and_return(true) + + Chef::Node.stub!(:load).and_return(@node) + end + + describe "run" do + it "should load the node" do + Chef::Node.should_receive(:load).with("adam").and_return(@node) + @knife.run + end + + it "should remove the item from the run list" do + @knife.run + @node.run_list[0].should_not == 'role[monkey]' + end + + it "should save the node" do + @node.should_receive(:save).and_return(true) + @knife.run + end + + it "should print the run list" do + @knife.config[:print_after] = true + @knife.ui.should_receive(:output).with({ "knifetest-node" => { 'run_list' => [] } }) + @knife.run + end + + describe "run with a list of roles and recipes" do + it "should remove the items from the run list" do + @node.run_list << 'role[monkey]' + @node.run_list << 'recipe[duck::type]' + @knife.name_args = [ 'adam', 'role[monkey],recipe[duck::type]' ] + @knife.run + @node.run_list.should_not include('role[monkey]') + @node.run_list.should_not include('recipe[duck::type]') + end + end + end +end + + + diff --git a/spec/unit/knife/node_show_spec.rb b/spec/unit/knife/node_show_spec.rb new file mode 100644 index 0000000000..6600b2aa96 --- /dev/null +++ b/spec/unit/knife/node_show_spec.rb @@ -0,0 +1,48 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Copyright:: Copyright (c) 2008 Opscode, 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::NodeShow do + before(:each) do + Chef::Config[:node_name] = "webmonkey.example.com" + @knife = Chef::Knife::NodeShow.new + @knife.config = { + :attribute => nil, + :run_list => nil, + :environment => nil + } + @knife.name_args = [ "adam" ] + @knife.stub!(:output).and_return(true) + @node = Chef::Node.new() + Chef::Node.stub!(:load).and_return(@node) + end + + describe "run" do + it "should load the node" do + Chef::Node.should_receive(:load).with("adam").and_return(@node) + @knife.run + end + + it "should pretty print the node, formatted for display" do + @knife.should_receive(:format_for_display).with(@node).and_return("poop") + @knife.should_receive(:output).with("poop") + @knife.run + end + end +end diff --git a/spec/unit/knife/role_bulk_delete_spec.rb b/spec/unit/knife/role_bulk_delete_spec.rb new file mode 100644 index 0000000000..0ee84f6455 --- /dev/null +++ b/spec/unit/knife/role_bulk_delete_spec.rb @@ -0,0 +1,80 @@ +# +# Author:: Stephen Delano (<stephen@opscode.com>) +# Copyright:: Copyright (c) 2010 Opscode, 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::RoleBulkDelete do + before(:each) do + Chef::Config[:node_name] = "webmonkey.example.com" + @knife = Chef::Knife::RoleBulkDelete.new + @knife.config = { + :print_after => nil + } + @knife.name_args = ["."] + @stdout = StringIO.new + @knife.ui.stub!(:stdout).and_return(@stdout) + @knife.ui.stub!(:confirm).and_return(true) + @roles = Hash.new + %w{dev staging production}.each do |role_name| + role = Chef::Role.new() + role.name(role_name) + role.stub!(:destroy).and_return(true) + @roles[role_name] = role + end + Chef::Role.stub!(:list).and_return(@roles) + end + + describe "run" do + + it "should get the list of the roles" do + Chef::Role.should_receive(:list).and_return(@roles) + @knife.run + end + + it "should print the roles you are about to delete" do + @knife.run + @stdout.string.should match(/#{@knife.ui.list(@roles.keys.sort, :columns_down)}/) + end + + it "should confirm you really want to delete them" do + @knife.ui.should_receive(:confirm) + @knife.run + end + + it "should delete each role" do + @roles.each_value do |r| + r.should_receive(:destroy) + end + @knife.run + end + + it "should only delete roles that match the regex" do + @knife.name_args = ["dev"] + @roles["dev"].should_receive(:destroy) + @roles["staging"].should_not_receive(:destroy) + @roles["production"].should_not_receive(:destroy) + @knife.run + end + + it "should exit if the regex is not provided" do + @knife.name_args = [] + lambda { @knife.run }.should raise_error(SystemExit) + end + + end +end diff --git a/spec/unit/knife/role_create_spec.rb b/spec/unit/knife/role_create_spec.rb new file mode 100644 index 0000000000..af3a6bf539 --- /dev/null +++ b/spec/unit/knife/role_create_spec.rb @@ -0,0 +1,80 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Copyright:: Copyright (c) 2008 Opscode, 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::RoleCreate do + before(:each) do + Chef::Config[:node_name] = "webmonkey.example.com" + @knife = Chef::Knife::RoleCreate.new + @knife.config = { + :description => nil + } + @knife.name_args = [ "adam" ] + @knife.stub!(:output).and_return(true) + @role = Chef::Role.new() + @role.stub!(:save) + Chef::Role.stub!(:new).and_return(@role) + @knife.stub!(:edit_data).and_return(@role) + @stdout = StringIO.new + @knife.ui.stub!(:stdout).and_return(@stdout) + end + + describe "run" do + it "should create a new role" do + Chef::Role.should_receive(:new).and_return(@role) + @knife.run + end + + it "should set the role name" do + @role.should_receive(:name).with("adam") + @knife.run + end + + it "should not print the role" do + @knife.should_not_receive(:output) + @knife.run + end + + it "should allow you to edit the data" do + @knife.should_receive(:edit_data).with(@role) + @knife.run + end + + it "should save the role" do + @role.should_receive(:save) + @knife.run + end + + describe "with -d or --description" do + it "should set the description" do + @knife.config[:description] = "All is bob" + @role.should_receive(:description).with("All is bob") + @knife.run + end + end + + describe "with -p or --print-after" do + it "should pretty print the node, formatted for display" do + @knife.config[:print_after] = true + @knife.should_receive(:output).with(@role) + @knife.run + end + end + end +end diff --git a/spec/unit/knife/role_delete_spec.rb b/spec/unit/knife/role_delete_spec.rb new file mode 100644 index 0000000000..d2d8b889b3 --- /dev/null +++ b/spec/unit/knife/role_delete_spec.rb @@ -0,0 +1,67 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Copyright:: Copyright (c) 2008 Opscode, 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::RoleDelete do + before(:each) do + Chef::Config[:node_name] = "webmonkey.example.com" + @knife = Chef::Knife::RoleDelete.new + @knife.config = { + :print_after => nil + } + @knife.name_args = [ "adam" ] + @knife.stub!(:output).and_return(true) + @knife.stub!(:confirm).and_return(true) + @role = Chef::Role.new() + @role.stub!(:destroy).and_return(true) + Chef::Role.stub!(:load).and_return(@role) + @stdout = StringIO.new + @knife.ui.stub!(:stdout).and_return(@stdout) + end + + describe "run" do + it "should confirm that you want to delete" do + @knife.should_receive(:confirm) + @knife.run + end + + it "should load the Role" do + Chef::Role.should_receive(:load).with("adam").and_return(@role) + @knife.run + end + + it "should delete the Role" do + @role.should_receive(:destroy).and_return(@role) + @knife.run + end + + it "should not print the Role" do + @knife.should_not_receive(:output) + @knife.run + end + + describe "with -p or --print-after" do + it "should pretty print the Role, formatted for display" do + @knife.config[:print_after] = true + @knife.should_receive(:output) + @knife.run + end + end + end +end diff --git a/spec/unit/knife/role_edit_spec.rb b/spec/unit/knife/role_edit_spec.rb new file mode 100644 index 0000000000..3a002f348c --- /dev/null +++ b/spec/unit/knife/role_edit_spec.rb @@ -0,0 +1,79 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Copyright:: Copyright (c) 2008 Opscode, 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::RoleEdit do + before(:each) do + Chef::Config[:node_name] = "webmonkey.example.com" + @knife = Chef::Knife::RoleEdit.new + @knife.config[:print_after] = nil + @knife.name_args = [ "adam" ] + @knife.ui.stub!(:output).and_return(true) + @role = Chef::Role.new() + @role.stub!(:save) + Chef::Role.stub!(:load).and_return(@role) + @knife.ui.stub!(:edit_data).and_return(@role) + @knife.ui.stub!(:msg) + end + + describe "run" do + it "should load the role" do + Chef::Role.should_receive(:load).with("adam").and_return(@role) + @knife.run + end + + it "should edit the role data" do + @knife.ui.should_receive(:edit_data).with(@role) + @knife.run + end + + it "should save the edited role data" do + pansy = Chef::Role.new + + @role.name("new_role_name") + @knife.ui.should_receive(:edit_data).with(@role).and_return(pansy) + pansy.should_receive(:save) + @knife.run + end + + it "should not save the unedited role data" do + pansy = Chef::Role.new + + @knife.ui.should_receive(:edit_data).with(@role).and_return(pansy) + pansy.should_not_receive(:save) + @knife.run + + end + + it "should not print the role" do + @knife.ui.should_not_receive(:output) + @knife.run + end + + describe "with -p or --print-after" do + it "should pretty print the role, formatted for display" do + @knife.config[:print_after] = true + @knife.ui.should_receive(:output).with(@role) + @knife.run + end + end + end +end + + diff --git a/spec/unit/knife/role_from_file_spec.rb b/spec/unit/knife/role_from_file_spec.rb new file mode 100644 index 0000000000..9b81bb14af --- /dev/null +++ b/spec/unit/knife/role_from_file_spec.rb @@ -0,0 +1,69 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Copyright:: Copyright (c) 2008 Opscode, 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' + +Chef::Knife::RoleFromFile.load_deps + +describe Chef::Knife::RoleFromFile do + before(:each) do + Chef::Config[:node_name] = "webmonkey.example.com" + @knife = Chef::Knife::RoleFromFile.new + @knife.config = { + :print_after => nil + } + @knife.name_args = [ "adam.rb" ] + @knife.stub!(:output).and_return(true) + @knife.stub!(:confirm).and_return(true) + @role = Chef::Role.new() + @role.stub!(:save) + @knife.loader.stub!(:load_from).and_return(@role) + @stdout = StringIO.new + @knife.ui.stub!(:stdout).and_return(@stdout) + end + + describe "run" do + it "should load from a file" do + @knife.loader.should_receive(:load_from).with('roles', 'adam.rb').and_return(@role) + @knife.run + end + + it "should not print the role" do + @knife.should_not_receive(:output) + @knife.run + end + + describe "with -p or --print-after" do + it "should print the role" do + @knife.config[:print_after] = true + @knife.should_receive(:output) + @knife.run + end + end + end + + describe "run with multiple arguments" do + it "should load each file" do + @knife.name_args = [ "adam.rb", "caleb.rb" ] + @knife.loader.should_receive(:load_from).with('roles', 'adam.rb').and_return(@role) + @knife.loader.should_receive(:load_from).with('roles', 'caleb.rb').and_return(@role) + @knife.run + end + end + +end diff --git a/spec/unit/knife/role_list_spec.rb b/spec/unit/knife/role_list_spec.rb new file mode 100644 index 0000000000..1a5e8e2a72 --- /dev/null +++ b/spec/unit/knife/role_list_spec.rb @@ -0,0 +1,56 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Copyright:: Copyright (c) 2008 Opscode, 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::RoleList do + before(:each) do + Chef::Config[:node_name] = "webmonkey.example.com" + @knife = Chef::Knife::RoleList.new + @knife.stub!(:output).and_return(true) + @list = { + "foo" => "http://example.com/foo", + "bar" => "http://example.com/foo" + } + Chef::Role.stub!(:list).and_return(@list) + end + + describe "run" do + it "should list the roles" do + Chef::Role.should_receive(:list).and_return(@list) + @knife.run + end + + it "should pretty print the list" do + Chef::Role.should_receive(:list).and_return(@list) + @knife.should_receive(:output).with([ "bar", "foo" ]) + @knife.run + end + + describe "with -w or --with-uri" do + it "should pretty print the hash" do + @knife.config[:with_uri] = true + Chef::Role.should_receive(:list).and_return(@list) + @knife.should_receive(:output).with(@list) + @knife.run + end + end + end +end + + diff --git a/spec/unit/knife/ssh_spec.rb b/spec/unit/knife/ssh_spec.rb new file mode 100644 index 0000000000..6e90a87f01 --- /dev/null +++ b/spec/unit/knife/ssh_spec.rb @@ -0,0 +1,182 @@ +# +# Author:: Bryan McLellan <btm@opscode.com> +# Copyright:: Copyright (c) 2012 Opscode, 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' +require 'net/ssh' +require 'net/ssh/multi' + +describe Chef::Knife::Ssh do + before(:all) do + @original_config = Chef::Config.hash_dup + @original_knife_config = Chef::Config[:knife].dup + Chef::Config[:client_key] = CHEF_SPEC_DATA + "/ssl/private_key.pem" + end + + after(:all) do + Chef::Config.configuration = @original_config + Chef::Config[:knife] = @original_knife_config + end + + before do + @knife = Chef::Knife::Ssh.new + @knife.config.clear + @knife.config[:attribute] = "fqdn" + @node_foo = Chef::Node.new + @node_foo.automatic_attrs[:fqdn] = "foo.example.org" + @node_foo.automatic_attrs[:ipaddress] = "10.0.0.1" + @node_bar = Chef::Node.new + @node_bar.automatic_attrs[:fqdn] = "bar.example.org" + @node_bar.automatic_attrs[:ipaddress] = "10.0.0.2" + end + + describe "#configure_session" do + context "manual is set to false (default)" do + before do + @knife.config[:manual] = false + @query = Chef::Search::Query.new + end + + def configure_query(node_array) + @query.stub!(:search).and_return([node_array]) + Chef::Search::Query.stub!(:new).and_return(@query) + end + + def self.should_return_specified_attributes + it "returns an array of the attributes specified on the command line OR config file, if only one is set" do + @knife.config[:attribute] = "ipaddress" + @knife.config[:override_attribute] = "ipaddress" + configure_query([@node_foo, @node_bar]) + @knife.should_receive(:session_from_list).with(['10.0.0.1', '10.0.0.2']) + @knife.configure_session + end + + it "returns an array of the attributes specified on the command line even when a config value is set" do + @knife.config[:attribute] = "config_file" # this value will be the config file + @knife.config[:override_attribute] = "ipaddress" # this is the value of the command line via #configure_attribute + configure_query([@node_foo, @node_bar]) + @knife.should_receive(:session_from_list).with(['10.0.0.1', '10.0.0.2']) + @knife.configure_session + end + end + + it "searchs for and returns an array of fqdns" do + configure_query([@node_foo, @node_bar]) + @knife.should_receive(:session_from_list).with(['foo.example.org', 'bar.example.org']) + @knife.configure_session + end + + should_return_specified_attributes + + context "when cloud hostnames are available" do + before do + @node_foo.automatic_attrs[:cloud][:public_hostname] = "ec2-10-0-0-1.compute-1.amazonaws.com" + @node_bar.automatic_attrs[:cloud][:public_hostname] = "ec2-10-0-0-2.compute-1.amazonaws.com" + end + + it "returns an array of cloud public hostnames" do + configure_query([@node_foo, @node_bar]) + @knife.should_receive(:session_from_list).with(['ec2-10-0-0-1.compute-1.amazonaws.com', 'ec2-10-0-0-2.compute-1.amazonaws.com']) + @knife.configure_session + end + + should_return_specified_attributes + end + + it "should raise an error if no host are found" do + configure_query([ ]) + @knife.ui.should_receive(:fatal) + @knife.should_receive(:exit).with(10) + @knife.configure_session + end + + context "when there are some hosts found but they do not have an attribute to connect with" do + before do + @query.stub!(:search).and_return([[@node_foo, @node_bar]]) + @node_foo.automatic_attrs[:fqdn] = nil + @node_bar.automatic_attrs[:fqdn] = nil + Chef::Search::Query.stub!(:new).and_return(@query) + end + + it "should raise a specific error (CHEF-3402)" do + @knife.ui.should_receive(:fatal).with(/^2 nodes found/) + @knife.should_receive(:exit).with(10) + @knife.configure_session + end + end + end + + context "manual is set to true" do + before do + @knife.config[:manual] = true + end + + it "returns an array of provided values" do + @knife.instance_variable_set(:@name_args, ["foo.example.org bar.example.org"]) + @knife.should_receive(:session_from_list).with(['foo.example.org', 'bar.example.org']) + @knife.configure_session + end + end + end + + describe "#configure_attribute" do + before do + Chef::Config[:knife][:ssh_attribute] = nil + @knife.config[:attribute] = nil + end + + it "should return fqdn by default" do + @knife.configure_attribute + @knife.config[:attribute].should == "fqdn" + end + + it "should return the value set in the configuration file" do + Chef::Config[:knife][:ssh_attribute] = "config_file" + @knife.configure_attribute + @knife.config[:attribute].should == "config_file" + end + + it "should return the value set on the command line" do + @knife.config[:attribute] = "command_line" + @knife.configure_attribute + @knife.config[:attribute].should == "command_line" + end + + it "should set override_attribute to the value of attribute from the command line" do + @knife.config[:attribute] = "command_line" + @knife.configure_attribute + @knife.config[:attribute].should == "command_line" + @knife.config[:override_attribute].should == "command_line" + end + + it "should set override_attribute to the value of attribute from the config file" do + Chef::Config[:knife][:ssh_attribute] = "config_file" + @knife.configure_attribute + @knife.config[:attribute].should == "config_file" + @knife.config[:override_attribute].should == "config_file" + end + + it "should prefer the command line over the config file for the value of override_attribute" do + Chef::Config[:knife][:ssh_attribute] = "config_file" + @knife.config[:attribute] = "command_line" + @knife.configure_attribute + @knife.config[:override_attribute].should == "command_line" + end + end + +end + diff --git a/spec/unit/knife/status_spec.rb b/spec/unit/knife/status_spec.rb new file mode 100644 index 0000000000..b009997ab1 --- /dev/null +++ b/spec/unit/knife/status_spec.rb @@ -0,0 +1,43 @@ +# +# Author:: Sahil Muthoo (<sahil.muthoo@gmail.com>) +# Copyright:: Copyright (c) 2012 Opscode, 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' +require 'highline' + +describe Chef::Knife::Status do + before(:each) do + node = Chef::Node.new.tap do |n| + n.automatic_attrs["fqdn"] = "foobar" + n.automatic_attrs["ohai_time"] = 1343845969 + end + query = mock("Chef::Search::Query") + query.stub!(:search).and_yield(node) + Chef::Search::Query.stub!(:new).and_return(query) + @knife = Chef::Knife::Status.new + @stdout = StringIO.new + @knife.stub!(:highline).and_return(HighLine.new(StringIO.new, @stdout)) + end + + describe "run" do + it "should not colorize output unless it's writing to a tty" do + @knife.run + @stdout.string.match(/foobar/).should_not be_nil + @stdout.string.match(/\e.*ago/).should be_nil + end + end +end diff --git a/spec/unit/knife/tag_create_spec.rb b/spec/unit/knife/tag_create_spec.rb new file mode 100644 index 0000000000..925d060879 --- /dev/null +++ b/spec/unit/knife/tag_create_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe Chef::Knife::TagCreate do + before(:each) do + Chef::Config[:node_name] = "webmonkey.example.com" + @knife = Chef::Knife::TagCreate.new + @knife.name_args = [ Chef::Config[:node_name], "happytag" ] + + @node = Chef::Node.new + @node.stub! :save + Chef::Node.stub!(:load).and_return @node + @stdout = StringIO.new + @knife.ui.stub!(:stdout).and_return(@stdout) + end + + describe "run" do + it "can create tags on a node" do + @knife.run + @node.tags.should == ["happytag"] + @stdout.string.should match /created tags happytag.+node webmonkey.example.com/i + end + end +end diff --git a/spec/unit/knife/tag_delete_spec.rb b/spec/unit/knife/tag_delete_spec.rb new file mode 100644 index 0000000000..ca279033a4 --- /dev/null +++ b/spec/unit/knife/tag_delete_spec.rb @@ -0,0 +1,25 @@ +require 'spec_helper' + +describe Chef::Knife::TagDelete do + before(:each) do + Chef::Config[:node_name] = "webmonkey.example.com" + @knife = Chef::Knife::TagDelete.new + @knife.name_args = [ Chef::Config[:node_name], "sadtag" ] + + @node = Chef::Node.new + @node.stub! :save + @node.tags << "sadtag" << "happytag" + Chef::Node.stub!(:load).and_return @node + @stdout = StringIO.new + @knife.ui.stub!(:stdout).and_return(@stdout) + end + + describe "run" do + it "can delete tags on a node" do + @node.tags.should == ["sadtag", "happytag"] + @knife.run + @node.tags.should == ["happytag"] + @stdout.string.should match /deleted.+sadtag/i + end + end +end diff --git a/spec/unit/knife/tag_list_spec.rb b/spec/unit/knife/tag_list_spec.rb new file mode 100644 index 0000000000..0de5d5ebd8 --- /dev/null +++ b/spec/unit/knife/tag_list_spec.rb @@ -0,0 +1,23 @@ +require 'spec_helper' + +describe Chef::Knife::TagList do + before(:each) do + Chef::Config[:node_name] = "webmonkey.example.com" + @knife = Chef::Knife::TagList.new + @knife.name_args = [ Chef::Config[:node_name], "sadtag" ] + + @node = Chef::Node.new + @node.stub! :save + @node.tags << "sadtag" << "happytag" + Chef::Node.stub!(:load).and_return @node + end + + describe "run" do + it "can list tags on a node" do + expected = %w(sadtag happytag) + @node.tags.should == expected + @knife.should_receive(:output).with(expected) + @knife.run + end + end +end |