diff options
-rwxr-xr-x | chef/bin/knife-app | 26 | ||||
-rw-r--r-- | chef/lib/chef/application/knife.rb | 120 | ||||
-rw-r--r-- | chef/lib/chef/knife.rb | 168 | ||||
-rw-r--r-- | chef/lib/chef/knife/node_bulk_delete.rb | 64 | ||||
-rw-r--r-- | chef/lib/chef/knife/node_delete.rb | 45 | ||||
-rw-r--r-- | chef/lib/chef/knife/node_edit.rb | 69 | ||||
-rw-r--r-- | chef/lib/chef/knife/node_list.rb | 41 | ||||
-rw-r--r-- | chef/lib/chef/knife/node_run_list_add.rb | 64 | ||||
-rw-r--r-- | chef/lib/chef/knife/node_show.rb | 46 | ||||
-rw-r--r-- | chef/spec/spec_helper.rb | 5 | ||||
-rw-r--r-- | chef/spec/unit/application/knife_spec.rb | 48 | ||||
-rw-r--r-- | chef/spec/unit/knife/node_bulk_delete_spec.rb | 87 | ||||
-rw-r--r-- | chef/spec/unit/knife/node_delete_spec.rb | 67 | ||||
-rw-r--r-- | chef/spec/unit/knife/node_edit_spec.rb | 69 | ||||
-rw-r--r-- | chef/spec/unit/knife/node_list_spec.rb | 54 | ||||
-rw-r--r-- | chef/spec/unit/knife/node_run_list_add_spec.rb | 70 | ||||
-rw-r--r-- | chef/spec/unit/knife/node_show_spec.rb | 46 | ||||
-rw-r--r-- | chef/spec/unit/knife_spec.rb | 197 |
18 files changed, 1286 insertions, 0 deletions
diff --git a/chef/bin/knife-app b/chef/bin/knife-app new file mode 100755 index 0000000000..ab6c3d3104 --- /dev/null +++ b/chef/bin/knife-app @@ -0,0 +1,26 @@ +#!/usr/bin/env ruby +# +# ./knife - Chef CLI interface +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Copyright:: Copyright (c) 2009 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. + +$:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", "lib"))) + +require 'rubygems' +require 'chef/application/knife' + +Chef::Application::Knife.new.run diff --git a/chef/lib/chef/application/knife.rb b/chef/lib/chef/application/knife.rb new file mode 100644 index 0000000000..56a00f6ff9 --- /dev/null +++ b/chef/lib/chef/application/knife.rb @@ -0,0 +1,120 @@ +# +# Author:: Adam Jacob (<adam@opscode.com) +# Copyright:: Copyright (c) 2009 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 'chef/knife' +require 'chef/application' +require 'chef/client' +require 'chef/config' +require 'chef/log' +require 'chef/node' +require 'chef/role' +require 'chef/data_bag' +require 'chef/data_bag_item' +require 'chef/rest' +require 'chef/search/query' +require 'tmpdir' +require 'uri' + +class Chef::Application::Knife < Chef::Application + + banner "Usage: #{$0} sub-command (options)" + + option :config_file, + :short => "-c CONFIG", + :long => "--config CONFIG", + :default => "/etc/chef/client.rb", + :description => "The configuration file to use" + + option :log_level, + :short => "-l LEVEL", + :long => "--log_level LEVEL", + :description => "Set the log level (debug, info, warn, error, fatal)", + :default => :info, + :proc => lambda { |l| l.to_sym } + + option :log_location, + :short => "-L LOGLOCATION", + :long => "--logfile LOGLOCATION", + :description => "Set the log file location, defaults to STDOUT", + :proc => nil + + option :editor, + :short => "-e EDITOR", + :long => "--editor EDITOR", + :description => "Set the editor to use for interactive commands", + :default => ENV['EDITOR'] + + option :help, + :short => "-h", + :long => "--help", + :description => "Show this message", + :on => :tail, + :boolean => true, + :show_options => true, + :exit => 0 + + option :node_name, + :short => "-u USER", + :long => "--user USER", + :description => "API Client Username, defaults to OPSCODE_USER", + :default => ENV['OPSCODE_USER'] + + option :client_key, + :short => "-k KEY", + :long => "--key KEY", + :description => "API Client Key, defaults to OPSCODE_KEY", + :default => ENV['OPSCODE_KEY'] + + option :yes, + :short => "-y", + :long => "--yes", + :description => "Say yes to all prompts for confirmation" + + option :print_after, + :short => "-p", + :long => "--print-after", + :description => "Show the data after a destructive operation" + + option :version, + :short => "-v", + :long => "--version", + :description => "Show chef version", + :boolean => true, + :proc => lambda {|v| puts "Chef: #{::Chef::VERSION}"}, + :exit => 0 + + # Run knife + def run + if ARGV[0] =~ /^-/ + self.parse_options + Chef::Log.fatal("Sorry, you need to pass a sub-command first!") + puts self.opt_parser + puts + Chef::Knife.list_commands + exit 2 + elsif ARGV.length == 0 + self.parse_options + puts self.opt_parser + puts + Chef::Knife.list_commands + exit 1 + end + + knife = Chef::Knife.find_command(ARGV, self.class.options) + knife.run + end +end diff --git a/chef/lib/chef/knife.rb b/chef/lib/chef/knife.rb new file mode 100644 index 0000000000..ab1882333e --- /dev/null +++ b/chef/lib/chef/knife.rb @@ -0,0 +1,168 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Copyright:: Copyright (c) 2009 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 'mixlib/cli' +require 'chef/mixin/convert_to_class_name' + +class Chef + class Knife + include Mixlib::CLI + extend Chef::Mixin::ConvertToClassName + + attr_accessor :name_args + + # Load all the sub-commands + def self.load_commands + @sub_classes = Hash.new + Dir[ + File.expand_path(File.join(File.dirname(__FILE__), 'knife', '*.rb')) + ].each do |knife_file| + require knife_file + snake_case_file_name = File.basename(knife_file).sub(/\.rb$/, '') + @sub_classes[snake_case_file_name] = convert_to_class_name(snake_case_file_name) + end + @sub_classes + end + + def self.list_commands + load_commands + @sub_classes.keys.sort.each do |snake_case| + klass_instance = build_sub_class(snake_case) + klass_instance.parse_options + puts klass_instance.opt_parser + puts + end + end + + def self.build_sub_class(snake_case, merge_opts=nil) + klass = Chef::Knife.const_get(@sub_classes[snake_case]) + klass.options.merge!(merge_opts) if merge_opts + klass.new + end + + def self.find_command(args=ARGV, merge_opts={}) + load_commands + + non_dash_args = Array.new + args.each do |arg| + non_dash_args << arg if arg =~ /^([[:alpha:]]|_)+$/ + end + + to_try = non_dash_args.length + klass_instance = nil + cli_bits = nil + + while(to_try > 0) + cli_bits = non_dash_args[0..to_try] + snake_case_class_name = cli_bits.join("_") + + if @sub_classes.has_key?(snake_case_class_name) + klass_instance = build_sub_class(snake_case_class_name, merge_opts) + break + end + + to_try = to_try - 1 + end + + unless klass_instance + Chef::Log.fatal("Cannot find sub command for: #{args.join(' ')}") + Chef::Knife.list_commands + exit 10 + end + + extra = klass_instance.parse_options(args) + klass_instance.name_args = (extra - cli_bits) || [] + klass_instance.configure_chef + klass_instance + end + + def configure_chef + Chef::Config.from_file(config[:config_file]) if !config[:config_file].nil? && File.exists?(config[:config_file]) && File.readable?(config[:config_file]) + Chef::Config[:log_level] = config[:log_level] + Chef::Config[:log_location] = config[:log_location] || STDOUT + Chef::Config[:node_name] = config[:node_name] + Chef::Config[:client_key] = config[:client_key] + Chef::Log::Formatter.show_time = false + Chef::Log.init(Chef::Config[:log_location]) + Chef::Log.level(Chef::Config[:log_level]) + end + + def json_pretty_print(data) + puts JSON.pretty_generate(data) + end + + def format_list_for_display(list) + config[:with_uri] ? list : list.keys.sort { |a,b| a <=> b } + end + + def format_for_display(item) + data = item.kind_of?(Chef::DataBagItem) ? item.raw_data : item + + if config[:attribute] + config[:attribute].split(".").each do |attr| + data = data[attr] + end + { config[:attribute] => data.kind_of?(Chef::Node::Attribute) ? data.to_hash: data } + elsif config[:run_list] + data = data.run_list.run_list + { "run_list" => data } + else + data + end + end + + def edit_data(data, parse_output=true) + filename = "knife-edit-" + 0.upto(20) { filename += rand(9).to_s } + filename << ".js" + filename = File.join(Dir.tmpdir, filename) + tf = File.open(filename, "w") + tf.sync = true + tf.puts JSON.pretty_generate(data) + tf.close + raise "Please set EDITOR environment variable" unless system("#{config[:editor]} #{tf.path}") + tf = File.open(filename, "r") + output = tf.gets(nil) + tf.close + File.unlink(filename) + + parse_output ? JSON.parse(output) : output + end + + def confirm(question) + return true if config[:yes] + + print "#{question}? (Y/N) " + answer = STDIN.readline + answer.chomp! + case answer + when "Y", "y" + true + when "N", "n" + Chef::Log.info("You said no, so I'm done here.") + exit 3 + else + Chef::Log.error("I have no idea what to do with #{answer}") + Chef::Log.error("Just say Y or N, please.") + confirm(question) + end + end + + end +end + diff --git a/chef/lib/chef/knife/node_bulk_delete.rb b/chef/lib/chef/knife/node_bulk_delete.rb new file mode 100644 index 0000000000..9e5ff49f29 --- /dev/null +++ b/chef/lib/chef/knife/node_bulk_delete.rb @@ -0,0 +1,64 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Copyright:: Copyright (c) 2009 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 'chef/knife' +require 'chef/node' +require 'json' + +class Chef + class Knife + class NodeBulkDelete < Knife + + banner "Sub-Command: node bulk delete (options)" + + option :regex, + :short => "-r [REGEX]", + :long => "--regex [REGEX]", + :description => "Narrow the operation via regular expression" + + def run + nodes = Chef::Node.list(true) + + if config[:regex] + to_delete = Hash.new + nodes.each_key do |node_name| + next if config[:regex] && node_name !~ /#{config[:regex]}/ + to_delete[node_name] = nodes[node_name] + end + else + to_delete = nodes + end + + json_pretty_print(format_list_for_display(to_delete)) + + confirm("Do you really want to delete the above nodes") + + to_delete.each do |name, node| + node.destroy + json_pretty_print(format_for_display(node)) if config[:print_after] + Chef::Log.warn("Deleted node #{name}") + end + end + + end + end +end + + + + diff --git a/chef/lib/chef/knife/node_delete.rb b/chef/lib/chef/knife/node_delete.rb new file mode 100644 index 0000000000..a9894689df --- /dev/null +++ b/chef/lib/chef/knife/node_delete.rb @@ -0,0 +1,45 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Copyright:: Copyright (c) 2009 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 'chef/knife' +require 'chef/node' +require 'json' + +class Chef + class Knife + class NodeDelete < Knife + + banner "Sub-Command: node delete NODE (options)" + + def run + confirm("Do you really want to delete #{@name_args[0]}") + + node = Chef::Node.load(@name_args[0]) + node.destroy + + json_pretty_print(format_for_display(node)) if config[:print_after] + + Chef::Log.warn("Deleted node #{@name_args[0]}!") + end + + end + end +end + + + diff --git a/chef/lib/chef/knife/node_edit.rb b/chef/lib/chef/knife/node_edit.rb new file mode 100644 index 0000000000..834718d682 --- /dev/null +++ b/chef/lib/chef/knife/node_edit.rb @@ -0,0 +1,69 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Copyright:: Copyright (c) 2009 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 'chef/knife' +require 'chef/node' +require 'json' + +class Chef + class Knife + class NodeEdit < Knife + + banner "Sub-Command: node edit NODE (options)" + + option :attribute, + :short => "-a [ATTR]", + :long => "--attribute [ATTR]", + :description => "Edit only one attribute" + + def run + node = Chef::Node.load(@name_args[0]) + + if config[:attribute] + attr_bits = config[:attribute].split(".") + to_edit = node + attr_bits.each do |attr| + to_edit = to_edit[attr] + end + + edited_data = edit_data(to_edit) + + walker = node + attr_bits.each_index do |i| + if (attr_bits.length - 1) == i + walker[attr_bits[i]] = edited_data + else + walker = walker[attr_bits[i]] + end + end + new_node = node + else + new_node = edit_data(node) + end + + new_node.save + + Chef::Log.info("Saved #{new_node}") + + json_pretty_print(format_for_display(node)) if config[:print_after] + end + end + end +end + + diff --git a/chef/lib/chef/knife/node_list.rb b/chef/lib/chef/knife/node_list.rb new file mode 100644 index 0000000000..5239d2c29d --- /dev/null +++ b/chef/lib/chef/knife/node_list.rb @@ -0,0 +1,41 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Copyright:: Copyright (c) 2009 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 'chef/knife' +require 'chef/node' +require 'json' + +class Chef + class Knife + class NodeList < Knife + + banner "Sub-Command: node list (options)" + + option :with_uri, + :short => "-w", + :long => "--with-uri", + :description => "Show corresponding URIs" + + def run + json_pretty_print(format_list_for_display(Chef::Node.list)) + end + end + end +end + + diff --git a/chef/lib/chef/knife/node_run_list_add.rb b/chef/lib/chef/knife/node_run_list_add.rb new file mode 100644 index 0000000000..07ebfd2521 --- /dev/null +++ b/chef/lib/chef/knife/node_run_list_add.rb @@ -0,0 +1,64 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Copyright:: Copyright (c) 2009 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 'chef/knife' +require 'chef/node' +require 'json' + +class Chef + class Knife + class NodeRunListAdd < Knife + + banner "Sub-Command: node run_list add [NODE] [ENTRY] (options)" + + option :after, + :short => "-a [ITEM]", + :long => "--after [ITEM]", + :description => "Place the ENTRY in the run list after ITEM" + + def run + node = Chef::Node.load(@name_args[0]) + entry = @name_args[1] + + add_to_run_list(node, entry, config[:after]) + + node.save + + config[:run_list] = true + + json_pretty_print(format_for_display(node)) + end + + def add_to_run_list(node, new_value, after=nil) + if after + nlist = [] + node.run_list.each do |entry| + nlist << entry + if entry == after + nlist << new_value + end + end + node.run_list.reset(nlist) + else + node.run_list << new_value + end + end + + end + end +end diff --git a/chef/lib/chef/knife/node_show.rb b/chef/lib/chef/knife/node_show.rb new file mode 100644 index 0000000000..1c7d29a037 --- /dev/null +++ b/chef/lib/chef/knife/node_show.rb @@ -0,0 +1,46 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Copyright:: Copyright (c) 2009 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 'chef/knife' +require 'chef/node' +require 'json' + +class Chef + class Knife + class NodeShow < Knife + + banner "Sub-Command: node show NODE (options)" + + option :attribute, + :short => "-a [ATTR]", + :long => "--attribute [ATTR]", + :description => "Show only one attribute" + + option :run_list, + :short => "-r", + :long => "--run-list", + :description => "Show only the run list" + + def run + node = Chef::Node.load(@name_args[0]) + json_pretty_print(format_for_display(node)) + end + end + end +end + diff --git a/chef/spec/spec_helper.rb b/chef/spec/spec_helper.rb index 917ad74fe2..e62e660342 100644 --- a/chef/spec/spec_helper.rb +++ b/chef/spec/spec_helper.rb @@ -47,3 +47,8 @@ Chef::Config[:cache_options] = { } Chef::Log.level(Chef::Config.log_level) Chef::Config.solo(false) +def redefine_argv(value) + Object.send(:remove_const, :ARGV) + Object.send(:const_set, :ARGV, value) +end + diff --git a/chef/spec/unit/application/knife_spec.rb b/chef/spec/unit/application/knife_spec.rb new file mode 100644 index 0000000000..dfe6c6d60c --- /dev/null +++ b/chef/spec/unit/application/knife_spec.rb @@ -0,0 +1,48 @@ +# +# Author:: AJ Christensen (<aj@junglist.gen.nz>) +# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper")) + +describe Chef::Application::Knife do + before(:each) do + @knife = Chef::Application::Knife.new + @orig_argv ||= ARGV + redefine_argv([]) + end + + after(:each) do + redefine_argv(@orig_argv) + end + + describe "run" do + it "should exit 1 and print the options if no arguments are given at all" do + lambda { @knife.run }.should raise_error(SystemExit) { |e| e.status.should == 1 } + end + + it "should exit 2 if run without a sub command" do + redefine_argv([ "--user", "adam" ]) + lambda { @knife.run }.should raise_error(SystemExit) { |e| e.status.should == 2 } + end + + it "should run a sub command with the applications command line option prototype" do + redefine_argv([ "node", "show", "latte.local" ]) + knife = mock(Chef::Knife, :null_object => true) + Chef::Knife.should_receive(:find_command).with(ARGV, Chef::Application::Knife.options).and_return(knife) + @knife.run + end + end +end diff --git a/chef/spec/unit/knife/node_bulk_delete_spec.rb b/chef/spec/unit/knife/node_bulk_delete_spec.rb new file mode 100644 index 0000000000..4eb7ad547d --- /dev/null +++ b/chef/spec/unit/knife/node_bulk_delete_spec.rb @@ -0,0 +1,87 @@ +# +# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper")) + +describe Chef::Knife::NodeBulkDelete do + before(:each) do + @knife = Chef::Knife::NodeBulkDelete.new + @knife.config = { + :print_after => nil + } + @knife.name_args = [] + @knife.stub!(:json_pretty_print).and_return(true) + @knife.stub!(:confirm).and_return(true) + @nodes = Hash.new + %w{adam brent jacob}.each do |node_name| + node = Chef::Node.new() + node.name(node_name) + node.stub!(:destroy).and_return(true) + @nodes[node_name] = node + end + Chef::Node.stub!(:list).and_return(@nodes) + end + + describe "run" do + + it "should get the list of inflated nodes" do + Chef::Node.should_receive(:list).and_return(@nodes) + @knife.run + end + + it "should print the nodes you are about to delete" do + @knife.should_receive(:json_pretty_print).with(@knife.format_list_for_display(@nodes)) + @knife.run + end + + it "should confirm you really want to delete them" do + @knife.should_receive(:confirm) + @knife.run + end + + it "should delete each node" do + @nodes.each_value do |n| + n.should_receive(:destroy) + end + @knife.run + end + + describe "with -r or --regex" do + it "should only delete nodes that match the regex" do + @knife.config[:regex] = 'adam' + @nodes['adam'].should_receive(:destroy) + @nodes['brent'].should_not_receive(:destroy) + @nodes['jacob'].should_not_receive(:destroy) + @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 + @nodes.each_value do |n| + @knife.should_receive(:json_pretty_print).with(@knife.format_for_display(n)) + end + @knife.run + end + end + end +end + + + diff --git a/chef/spec/unit/knife/node_delete_spec.rb b/chef/spec/unit/knife/node_delete_spec.rb new file mode 100644 index 0000000000..a8a410cafa --- /dev/null +++ b/chef/spec/unit/knife/node_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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper")) + +describe Chef::Knife::NodeDelete do + before(:each) do + @knife = Chef::Knife::NodeDelete.new + @knife.config = { + :print_after => nil + } + @knife.name_args = [ "adam" ] + @knife.stub!(:json_pretty_print).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) + 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(:json_pretty_print).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(:json_pretty_print).with("poop") + @knife.run + end + end + end +end + + diff --git a/chef/spec/unit/knife/node_edit_spec.rb b/chef/spec/unit/knife/node_edit_spec.rb new file mode 100644 index 0000000000..9a2f16a3d8 --- /dev/null +++ b/chef/spec/unit/knife/node_edit_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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper")) + +describe Chef::Knife::NodeEdit do + before(:each) do + @knife = Chef::Knife::NodeEdit.new + @knife.config = { + :attribute => nil, + :print_after => nil + } + @knife.name_args = [ "adam" ] + @knife.stub!(:json_pretty_print).and_return(true) + @node = Chef::Node.new() + @node.stub!(:save) + Chef::Node.stub!(:load).and_return(@node) + @knife.stub!(:edit_data).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 edit the node data" do + @knife.should_receive(:edit_data).with(@node) + @knife.run + end + + it "should save the edited node data" do + pansy = Chef::Node.new + @knife.should_receive(:edit_data).with(@node).and_return(pansy) + pansy.should_receive(:save) + @knife.run + end + + it "should not print the node" do + @knife.should_not_receive(:json_pretty_print).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(:json_pretty_print).with("poop") + @knife.run + end + end + end +end + diff --git a/chef/spec/unit/knife/node_list_spec.rb b/chef/spec/unit/knife/node_list_spec.rb new file mode 100644 index 0000000000..2fbcf765a4 --- /dev/null +++ b/chef/spec/unit/knife/node_list_spec.rb @@ -0,0 +1,54 @@ +# +# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper")) + +describe Chef::Knife::NodeList do + before(:each) do + @knife = Chef::Knife::NodeList.new + @knife.stub!(:json_pretty_print).and_return(true) + @list = { + "foo" => "http://example.com/foo", + "bar" => "http://example.com/foo" + } + Chef::Node.stub!(:list).and_return(@list) + end + + describe "run" do + it "should list the nodes" 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(:json_pretty_print).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::Node.should_receive(:list).and_return(@list) + @knife.should_receive(:json_pretty_print).with(@list) + @knife.run + end + end + end +end + diff --git a/chef/spec/unit/knife/node_run_list_add_spec.rb b/chef/spec/unit/knife/node_run_list_add_spec.rb new file mode 100644 index 0000000000..bc3b1a8a65 --- /dev/null +++ b/chef/spec/unit/knife/node_run_list_add_spec.rb @@ -0,0 +1,70 @@ +# +# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper")) + +describe Chef::Knife::NodeRunListAdd do + before(:each) do + @knife = Chef::Knife::NodeRunListAdd.new + @knife.config = { + :after => nil + } + @knife.name_args = [ "adam", "role[monkey]" ] + @knife.stub!(:json_pretty_print).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(:json_pretty_print).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 + end +end + + + diff --git a/chef/spec/unit/knife/node_show_spec.rb b/chef/spec/unit/knife/node_show_spec.rb new file mode 100644 index 0000000000..b711b66e2b --- /dev/null +++ b/chef/spec/unit/knife/node_show_spec.rb @@ -0,0 +1,46 @@ +# +# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper")) + +describe Chef::Knife::NodeShow do + before(:each) do + @knife = Chef::Knife::NodeShow.new + @knife.config = { + :attribute => nil, + :run_list => nil + } + @knife.name_args = [ "adam" ] + @knife.stub!(:json_pretty_print).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(:json_pretty_print).with("poop") + @knife.run + end + end +end diff --git a/chef/spec/unit/knife_spec.rb b/chef/spec/unit/knife_spec.rb new file mode 100644 index 0000000000..877b510dec --- /dev/null +++ b/chef/spec/unit/knife_spec.rb @@ -0,0 +1,197 @@ +# +# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper")) + +describe Chef::Knife do + before(:each) do + @knife = Chef::Knife.new + end + + describe "class method" do + describe "load_commands" do + it "should require all the sub commands" do + sub_classes = Chef::Knife.load_commands + sub_classes.should have_key("node_show") + sub_classes["node_show"].should == "NodeShow" + end + end + + describe "list_commands" do + before(:each) do + @orig_argv ||= ARGV + redefine_argv([]) + end + + after(:each) do + redefine_argv(@orig_argv) + end + + it "should load commands" do + Chef::Knife.should_receive(:load_commands) + Chef::Knife.list_commands + end + end + + describe "build_sub_class" do + before(:each) do + Chef::Knife.load_commands + end + + it "should build a sub class" do + Chef::Knife.build_sub_class("node_show").should be_a_kind_of(Chef::Knife::NodeShow) + end + + it "should not merge options if none are passed" do + Chef::Knife::NodeShow.options.should_not_receive(:merge!) + Chef::Knife.build_sub_class("node_show") + end + + it "should merge options if some are passed" do + Chef::Knife::NodeShow.options.should_receive(:merge!).with(Chef::Application::Knife.options) + Chef::Knife.build_sub_class("node_show", Chef::Application::Knife.options) + end + end + + describe "find_command" do + before(:each) do + @args = [ "node", "show", "computron" ] + @sub_class = Chef::Knife::NodeShow.new + @sub_class.stub!(:parse_options).and_return([@args[-1]]) + @sub_class.stub!(:configure_chef).and_return(true) + Chef::Knife.stub!(:build_sub_class).and_return(@sub_class) + end + + it "should find the most appropriate class" do + Chef::Knife.should_receive(:build_sub_class).with("node_show", {}).and_return(@sub_class) + Chef::Knife.find_command(@args).should be_a_kind_of(Chef::Knife::NodeShow) + end + + it "should parse the configuration arguments" do + @sub_class.should_receive(:parse_options).with(@args) + Chef::Knife.find_command(@args) + end + + it "should set the name args" do + @sub_class.should_receive(:name_args=).with([@args[-1]]) + Chef::Knife.find_command(@args) + end + + it "should exit 10 if the sub command is not found" do + Chef::Knife.stub!(:list_commands).and_return(true) + lambda { + Chef::Knife.find_command([ "monkey", "man" ]) + }.should raise_error(SystemExit) { |e| e.status.should == 10 } + end + end + end + + describe "initialize" do + it "should create a new Chef::Knife" do + @knife.should be_a_kind_of(Chef::Knife) + end + end + + describe "format_list_for_display" do + it "should print the full hash if --with-uri is true" do + @knife.config[:with_uri] = true + @knife.format_list_for_display({ :marcy => :playground }).should == { :marcy => :playground } + end + + it "should print only the keys if --with-uri is false" do + @knife.config[:with_uri] = false + @knife.format_list_for_display({ :marcy => :playground }).should == [ :marcy ] + end + end + + describe "format_for_display" do + it "should return the raw data" do + input = { :gi => :go } + @knife.format_for_display(input).should == input + end + + describe "with a data bag item" do + it "should use the raw data" do + dbi = mock(Chef::DataBagItem, :kind_of? => true) + dbi.should_receive(:raw_data).and_return({ "monkey" => "soup" }) + @knife.format_for_display(dbi).should == { "monkey" => "soup" } + end + end + + describe "with --attribute passed" do + it "should return the deeply nested attribute" do + input = { "gi" => { "go" => "ge" } } + @knife.config[:attribute] = "gi.go" + @knife.format_for_display(input).should == { "gi.go" => "ge" } + end + end + + describe "with --run-list passed" do + it "should return the run list" do + input = Chef::Node.new + input.run_list("role[monkey]", "role[churchmouse]") + @knife.config[:run_list] = true + response = @knife.format_for_display(input) + response["run_list"][0].should == "role[monkey]" + response["run_list"][1].should == "role[churchmouse]" + end + end + end + + describe "confirm" do + before(:each) do + @question = "monkeys rule" + Kernel.stub!(:print).and_return(true) + STDIN.stub!(:readline).and_return("y") + end + + it "should return true if you answer Y" do + STDIN.stub!(:readline).and_return("Y") + @knife.confirm(@question).should == true + end + + it "should return true if you answer y" do + STDIN.stub!(:readline).and_return("y") + @knife.confirm(@question).should == true + end + + it "should exit 3 if you answer N" do + STDIN.stub!(:readline).and_return("N") + lambda { + @knife.confirm(@question) + }.should raise_error(SystemExit) { |e| e.status.should == 3 } + end + + it "should exit 3 if you answer n" do + STDIN.stub!(:readline).and_return("n") + lambda { + @knife.confirm(@question) + }.should raise_error(SystemExit) { |e| e.status.should == 3 } + end + + describe "with --y or --yes passed" do + it "should return true" do + @knife.config[:yes] = true + @knife.confirm(@question).should == true + end + end + + end + +end + |