summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xchef/bin/knife-app26
-rw-r--r--chef/lib/chef/application/knife.rb120
-rw-r--r--chef/lib/chef/knife.rb168
-rw-r--r--chef/lib/chef/knife/node_bulk_delete.rb64
-rw-r--r--chef/lib/chef/knife/node_delete.rb45
-rw-r--r--chef/lib/chef/knife/node_edit.rb69
-rw-r--r--chef/lib/chef/knife/node_list.rb41
-rw-r--r--chef/lib/chef/knife/node_run_list_add.rb64
-rw-r--r--chef/lib/chef/knife/node_show.rb46
-rw-r--r--chef/spec/spec_helper.rb5
-rw-r--r--chef/spec/unit/application/knife_spec.rb48
-rw-r--r--chef/spec/unit/knife/node_bulk_delete_spec.rb87
-rw-r--r--chef/spec/unit/knife/node_delete_spec.rb67
-rw-r--r--chef/spec/unit/knife/node_edit_spec.rb69
-rw-r--r--chef/spec/unit/knife/node_list_spec.rb54
-rw-r--r--chef/spec/unit/knife/node_run_list_add_spec.rb70
-rw-r--r--chef/spec/unit/knife/node_show_spec.rb46
-rw-r--r--chef/spec/unit/knife_spec.rb197
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
+