diff options
190 files changed, 4559 insertions, 2176 deletions
diff --git a/.gitignore b/.gitignore index 7c2dc3213a..5d7ba78c01 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,4 @@ features/data/cookbooks/**/metadata.json features/data/solr/** erl_crash.dump *.rake_tasks~ +.idea
\ No newline at end of file diff --git a/CONTRIBUTING b/CONTRIBUTING new file mode 100644 index 0000000000..e359d0ebb8 --- /dev/null +++ b/CONTRIBUTING @@ -0,0 +1,16 @@ +Chef Project home pages: + +* Wiki: http://wiki.opscode.com/display/chef +* Product page: http://www.opscode.com/chef + +Chef Source code repository: + +* http://github.com/opscode/chef + +Opscode Open Source Ticket Tracking System: + +* http://tickets.opscode.com + +How to contribute to Chef: + +* http://wiki.opscode.com/display/opscode/Contributing diff --git a/chef-server-api/Rakefile b/chef-server-api/Rakefile index 1f3adc1444..79f824d37b 100644 --- a/chef-server-api/Rakefile +++ b/chef-server-api/Rakefile @@ -5,7 +5,7 @@ require 'merb-core' require 'merb-core/tasks/merb' GEM_NAME = "chef-server-api" -CHEF_SERVER_VERSION="0.8.11" +CHEF_SERVER_VERSION="0.8.12" AUTHOR = "Opscode" EMAIL = "chef@opscode.com" HOMEPAGE = "http://wiki.opscode.com/display/chef" @@ -22,16 +22,18 @@ spec = Gem::Specification.new do |s| s.author = AUTHOR s.email = EMAIL s.homepage = HOMEPAGE - + s.add_dependency "merb-core", "~> 1.0.0" s.add_dependency "merb-slices", "~> 1.0.0" s.add_dependency "merb-assets", "~> 1.0.0" s.add_dependency "merb-helpers", "~> 1.0.0" - ["thin", - "json", - "uuidtools" - ].each { |g| s.add_dependency g} - + + s.add_dependency "json", "<= 1.4.2" + + s.add_dependency "uuidtools", "~> 2.1.1" + + s.add_dependency "thin" + s.require_path = 'lib' s.files = %w(LICENSE README.rdoc Rakefile) + Dir.glob("{config,lib,spec,app,public,stubs}/**/*") end diff --git a/chef-server-api/app/controllers/application.rb b/chef-server-api/app/controllers/application.rb index 6db4dda034..f6236b8511 100644 --- a/chef-server-api/app/controllers/application.rb +++ b/chef-server-api/app/controllers/application.rb @@ -88,6 +88,14 @@ class ChefServerApi::Application < Merb::Controller end end + def is_admin_or_validator + if @auth_user.admin || @auth_user.name == Chef::Config[:validation_client_name] + true + else + raise Unauthorized, "You are not allowed to take this action." + end + end + def is_correct_node if @auth_user.admin || @auth_user.name == params[:id] true diff --git a/chef-server-api/app/controllers/clients.rb b/chef-server-api/app/controllers/clients.rb index 4e7cfbe0c9..589d74c4a6 100644 --- a/chef-server-api/app/controllers/clients.rb +++ b/chef-server-api/app/controllers/clients.rb @@ -23,8 +23,9 @@ class ChefServerApi::Clients < ChefServerApi::Application provides :json before :authenticate_every - before :is_admin, :only => :index - before :is_correct_node, :only => [ :show, :create, :update, :destroy ] + before :is_admin, :only => [ :index, :update, :destroy ] + before :is_admin_or_validator, :only => [ :create ] + before :is_correct_node, :only => [ :show ] # GET /clients def index @@ -48,7 +49,13 @@ class ChefServerApi::Clients < ChefServerApi::Application exists = true if params.has_key?(:inflated_object) params[:name] ||= params[:inflated_object].name - params[:admin] ||= params[:inflated_object].admin + # We can only get here if we're admin or the validator. Only + # allow creating admin clients if we're already an admin. + if @auth_user.admin + params[:admin] ||= params[:inflated_object].admin + else + params[:admin] = false + end end begin diff --git a/chef-server-api/lib/chef-server-api.rb b/chef-server-api/lib/chef-server-api.rb index 3c9157a60e..7d5dac559c 100644 --- a/chef-server-api/lib/chef-server-api.rb +++ b/chef-server-api/lib/chef-server-api.rb @@ -75,7 +75,7 @@ if defined?(Merb::Plugins) Chef::Certificate.gen_validation_key # Generate the Web UI Key - Chef::Certificate.gen_validation_key(Chef::Config[:web_ui_client_name], Chef::Config[:web_ui_key]) + Chef::Certificate.gen_validation_key(Chef::Config[:web_ui_client_name], Chef::Config[:web_ui_key], true) Chef::Log.info('Loading roles') Chef::Role.sync_from_disk_to_couchdb diff --git a/chef-server-webui/Rakefile b/chef-server-webui/Rakefile index c9e53085de..8b9051036a 100644 --- a/chef-server-webui/Rakefile +++ b/chef-server-webui/Rakefile @@ -5,7 +5,7 @@ require 'merb-core' require 'merb-core/tasks/merb' GEM_NAME = "chef-server-webui" -CHEF_SERVER_VERSION="0.8.11" +CHEF_SERVER_VERSION="0.8.12" AUTHOR = "Opscode" EMAIL = "chef@opscode.com" HOMEPAGE = "http://wiki.opscode.com/display/chef" @@ -29,13 +29,11 @@ spec = Gem::Specification.new do |s| s.add_dependency "merb-helpers", "~> 1.0.0" s.add_dependency "merb-haml", "~> 1.0.0" s.add_dependency "merb-param-protection", "~> 1.0.0" - - ["thin", - "haml", - "json", - "ruby-openid", - "coderay"].each { |g| s.add_dependency g} - + + s.add_dependency "json", "<= 1.4.2" + + %w{thin haml ruby-openid coderay}.each { |g| s.add_dependency g} + s.require_path = 'lib' s.files = %w(LICENSE README.rdoc Rakefile config.ru) + Dir.glob("{bin,config,lib,spec,app,public,stubs}/**/*") end diff --git a/chef-server-webui/app/helpers/application_helper.rb b/chef-server-webui/app/helpers/application_helper.rb index 40d3772c7e..2036425c8e 100644 --- a/chef-server-webui/app/helpers/application_helper.rb +++ b/chef-server-webui/app/helpers/application_helper.rb @@ -114,19 +114,9 @@ module Merb count end - def syntax_highlight(code) - tokens = File.exists?(code) ? CodeRay.scan_file(code, :ruby) : CodeRay.scan(code, :ruby) - CodeRay.encode_tokens(tokens, :span) - end - - def get_file(uri) - r = Chef::REST.new(Chef::Config[:chef_server_url]) - content = r.get_rest(uri) - a = Tempfile.new("cookbook_temp_file") - File.open(a.path, 'w'){|f| f.write(content)} - path = a.path - a.close - path + def syntax_hightlight(uri) + code = Chef::REST.new(Chef::Config[:chef_server_url]).get_rest(uri) + CodeRay.encode_tokens(CodeRay.scan(code, :ruby), :span) end def str_to_bool(str) diff --git a/chef-server-webui/app/views/cookbooks/show.html.haml b/chef-server-webui/app/views/cookbooks/show.html.haml index 94fbff8496..a1a3799497 100644 --- a/chef-server-webui/app/views/cookbooks/show.html.haml +++ b/chef-server-webui/app/views/cookbooks/show.html.haml @@ -4,37 +4,37 @@ .inner .accordion - unless @cookbook["libraries"].empty? - %h2.head= link_to "Library Files", "#" + %h2.head= link_to "Library Files", "JavaScript:void(0);" .files - @cookbook["libraries"].each do |f| .code - %h4.head= link_to File.basename(f["name"]), "#" - %pre.ruby= syntax_highlight(get_file(f["uri"])) + %h4.head= link_to File.basename(f["name"]), "JavaScript:void(0);" + %pre.ruby= syntax_highlight(f["uri"]) - unless @cookbook["attributes"].empty? - %h2.head= link_to "Attribute Files", "#" + %h2.head= link_to "Attribute Files", "JavaScript:void(0);" .files - @cookbook["attributes"].each do |f| .code - %h4.head= link_to File.basename(f["name"]), "#" - %pre.ruby= syntax_highlight(get_file(f["uri"])) + %h4.head= link_to File.basename(f["name"]), "JavaScript:void(0);" + %pre.ruby= syntax_highlight(f["uri"]) - unless @cookbook["definitions"].empty? - %h2.head= link_to "Definition Files", "#" + %h2.head= link_to "Definition Files", "JavaScript:void(0);" .files - @cookbook["definitions"].each do |f| .code - %h4.head= link_to File.basename(f["name"]), "#" - %pre.ruby= syntax_highlight(get_file(f["uri"])) + %h4.head= link_to File.basename(f["name"]), "JavaScript:void(0);" + %pre.ruby= syntax_highlight(f["uri"]) - unless @cookbook["recipes"].empty? - %h2.head= link_to "Recipe Files", "#" + %h2.head= link_to "Recipe Files", "JavaScript:void(0);" .files - @cookbook["recipes"].each do |f| .code - %h4.head= link_to File.basename(f["name"]), "#" - %pre.ruby= syntax_highlight(get_file(f["uri"])) + %h4.head= link_to File.basename(f["name"]), "JavaScript:void(0);" + %pre.ruby= syntax_highlight(f["uri"]) - unless @cookbook["templates"].empty? - %h2.head= link_to "Template Files", "#" + %h2.head= link_to "Template Files", "JavaScript:void(0);" .files - @cookbook["templates"].each do |f| .code - %h4.head= link_to File.basename(f["name"]), "#" - %pre.ruby= syntax_highlight(get_file(f["uri"]))
\ No newline at end of file + %h4.head= link_to File.basename(f["name"]), "JavaScript:void(0);" + %pre.ruby= syntax_highlight(f["uri"])
\ No newline at end of file diff --git a/chef-server/Rakefile b/chef-server/Rakefile index 321efb7283..b1f6e68d68 100644 --- a/chef-server/Rakefile +++ b/chef-server/Rakefile @@ -18,21 +18,12 @@ require 'chef' unless defined?(Chef) include FileUtils GEM = "chef-server" -CHEF_SERVER_VERSION = "0.8.11" +CHEF_SERVER_VERSION = "0.8.12" AUTHOR = "Opscode" EMAIL = "chef@opscode.com" HOMEPAGE = "http://wiki.opscode.com/display/chef" SUMMARY = "A systems integration framework, built to bring the benefits of configuration management to your entire infrastructure." -# Load the basic runtime dependencies; this will include -# any plugins and therefore plugin rake tasks. -init_env = ENV['MERB_ENV'] || 'rake' -Merb.load_dependencies(:environment => init_env) - -# Get Merb plugins and dependencies -Merb::Plugins.rakefiles.each { |r| require r } - -# Load any app level custom rakefile extensions from lib/tasks tasks_path = File.join(File.dirname(__FILE__), "lib", "tasks") rake_files = Dir["#{tasks_path}/*.rake"] rake_files.each{|rake_file| load rake_file } diff --git a/chef-server/bin/chef-server b/chef-server/bin/chef-server index bcdbca5a44..18710efdb1 100755 --- a/chef-server/bin/chef-server +++ b/chef-server/bin/chef-server @@ -26,7 +26,7 @@ require "rubygems" require "merb-core" -CHEF_SERVER_VERSION = "0.8.11" +CHEF_SERVER_VERSION = "0.8.12" [ 'chef', 'chef-server-api' ].each do |lib| $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "lib"))) diff --git a/chef-server/bin/chef-server-webui b/chef-server/bin/chef-server-webui index 3f728bc870..e3f908370f 100755 --- a/chef-server/bin/chef-server-webui +++ b/chef-server/bin/chef-server-webui @@ -32,7 +32,7 @@ require "merb-core" require library if File.exists?(library) end -CHEF_SERVER_VERSION = "0.8.11" +CHEF_SERVER_VERSION = "0.8.12" # Ensure the chef gem we load is the same version as the chef server unless defined?(Chef) diff --git a/chef-server/lib/tasks/package.rake b/chef-server/lib/tasks/package.rake index c36865d854..6560481640 100644 --- a/chef-server/lib/tasks/package.rake +++ b/chef-server/lib/tasks/package.rake @@ -16,11 +16,13 @@ spec = Gem::Specification.new do |s| s.add_dependency "merb-haml", "~> 1.0.0" s.add_dependency "merb-assets", "~> 1.0.0" s.add_dependency "merb-helpers", "~> 1.0.0" + s.add_dependency "json", "<= 1.4.2" + %w{ thin haml - ruby-openid json coderay}.each { |gem| s.add_dependency gem } - + ruby-openid coderay}.each { |gem| s.add_dependency gem } + s.bindir = "bin" - s.executables = %w( chef-server chef-server-webui ) + s.executables = %w( chef-server chef-server-webui ) s.files = %w(LICENSE README.rdoc config.ru config-webui.ru) + Dir.glob("{app,bin,config,lib,public}/**/*") end diff --git a/chef-solr/VERSION b/chef-solr/VERSION index 83ce05d72f..7eff8ab952 100644 --- a/chef-solr/VERSION +++ b/chef-solr/VERSION @@ -1 +1 @@ -0.8.11 +0.8.12 diff --git a/chef-solr/lib/chef/solr.rb b/chef-solr/lib/chef/solr.rb index 815f92aacb..8f397593a8 100644 --- a/chef-solr/lib/chef/solr.rb +++ b/chef-solr/lib/chef/solr.rb @@ -16,7 +16,6 @@ # limitations under the License. # -require 'rubygems' require 'chef/mixin/xml_escape' require 'chef/log' require 'chef/config' @@ -35,7 +34,7 @@ require 'uri' class Chef class Solr - VERSION = "0.8.11" + VERSION = "0.8.12" include Chef::Mixin::XMLEscape diff --git a/chef-solr/lib/chef/solr/application/solr.rb b/chef-solr/lib/chef/solr/application/solr.rb index 8042c76f88..c77003c1e1 100644 --- a/chef-solr/lib/chef/solr/application/solr.rb +++ b/chef-solr/lib/chef/solr/application/solr.rb @@ -114,25 +114,36 @@ class Chef def initialize super - Chef::Log.level = Chef::Config[:log_level] end def setup_application - Chef::Daemon.change_privilege + # Need to redirect stdout and stderr so Java process inherits them. + # If -L wasn't specified, Chef::Config[:log_location] will be an IO + # object, otherwise it will be a String. + # + # Open this as a privileged user and hang onto it + if Chef::Config[:log_location].kind_of?(String) + @logfile = File.new(Chef::Config[:log_location], "a") + end + + Chef::Log.level = Chef::Config[:log_level] # Build up a client - c = Chef::Client.new - c.build_node(nil, true) + node = Chef::Node.new + node.platform = :default + node.platform_version = 42 + + Chef::Daemon.change_privilege solr_base = File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "..", "solr")) # Create the Jetty container unless File.directory?(Chef::Config[:solr_jetty_path]) Chef::Log.warn("Initializing the Jetty container") - solr_jetty_dir = Chef::Resource::Directory.new(Chef::Config[:solr_jetty_path], nil, c.node) + solr_jetty_dir = Chef::Resource::Directory.new(Chef::Config[:solr_jetty_path], nil, node) solr_jetty_dir.recursive(true) solr_jetty_dir.run_action(:create) - solr_jetty_untar = Chef::Resource::Execute.new("untar_jetty", nil, c.node) + solr_jetty_untar = Chef::Resource::Execute.new("untar_jetty", nil, node) solr_jetty_untar.command("tar zxvf #{File.join(solr_base, 'solr-jetty.tar.gz')}") solr_jetty_untar.cwd(Chef::Config[:solr_jetty_path]) solr_jetty_untar.run_action(:run) @@ -141,10 +152,10 @@ class Chef # Create the solr home unless File.directory?(Chef::Config[:solr_home_path]) Chef::Log.warn("Initializing Solr home directory") - solr_home_dir = Chef::Resource::Directory.new(Chef::Config[:solr_home_path], nil, c.node) + solr_home_dir = Chef::Resource::Directory.new(Chef::Config[:solr_home_path], nil, node) solr_home_dir.recursive(true) solr_home_dir.run_action(:create) - solr_jetty_untar = Chef::Resource::Execute.new("untar_solr_home", nil, c.node) + solr_jetty_untar = Chef::Resource::Execute.new("untar_solr_home", nil, node) solr_jetty_untar.command("tar zxvf #{File.join(solr_base, 'solr-home.tar.gz')}") solr_jetty_untar.cwd(Chef::Config[:solr_home_path]) solr_jetty_untar.run_action(:run) @@ -153,7 +164,7 @@ class Chef # Create the solr data path unless File.directory?(Chef::Config[:solr_data_path]) Chef::Log.warn("Initializing Solr data directory") - solr_data_dir = Chef::Resource::Directory.new(Chef::Config[:solr_data_path], nil, c.node) + solr_data_dir = Chef::Resource::Directory.new(Chef::Config[:solr_data_path], nil, node) solr_data_dir.recursive(true) solr_data_dir.run_action(:create) end @@ -164,15 +175,6 @@ class Chef Chef::Daemon.daemonize("chef-solr") end - # Need to redirect stdout and stderr so Java process inherits them. - # If -L wasn't specified, Chef::Config[:log_location] will be an IO - # object, otherwise it will be a String. - if Chef::Config[:log_location].kind_of?(String) - logfile = File.new(Chef::Config[:log_location], "w") - STDOUT.reopen(logfile) - STDERR.reopen(logfile) - end - Dir.chdir(Chef::Config[:solr_jetty_path]) do command = "java -Xmx#{Chef::Config[:solr_heap_size]} -Xms#{Chef::Config[:solr_heap_size]}" command << " -Dsolr.data.dir=#{Chef::Config[:solr_data_path]}" @@ -180,6 +182,17 @@ class Chef command << " #{Chef::Config[:solr_java_opts]}" if Chef::Config[:solr_java_opts] command << " -jar #{File.join(Chef::Config[:solr_jetty_path], 'start.jar')}" Chef::Log.info("Starting Solr with #{command}") + + # Opened earlier before we dropped privileges + if @logfile + # Don't need it anymore + Chef::Log.close + + STDOUT.reopen(@logfile) + STDERR.reopen(@logfile) + @logfile.close + end + Kernel.exec(command) end diff --git a/chef-solr/lib/chef/solr/index_queue_consumer.rb b/chef-solr/lib/chef/solr/index_queue_consumer.rb index 45e665c6ad..7cff1d52a1 100644 --- a/chef-solr/lib/chef/solr/index_queue_consumer.rb +++ b/chef-solr/lib/chef/solr/index_queue_consumer.rb @@ -16,7 +16,6 @@ # limitations under the License. # -require 'rubygems' require 'chef/log' require 'chef/config' require 'chef/solr' diff --git a/chef/Rakefile b/chef/Rakefile index 8616b6d30a..a9b36da68c 100644 --- a/chef/Rakefile +++ b/chef/Rakefile @@ -4,7 +4,7 @@ require 'rake/rdoctask' require './tasks/rspec.rb' GEM = "chef" -CHEF_VERSION = "0.8.11" +CHEF_VERSION = "0.8.12" AUTHOR = "Adam Jacob" EMAIL = "adam@opscode.com" HOMEPAGE = "http://wiki.opscode.com/display/chef" @@ -29,7 +29,8 @@ spec = Gem::Specification.new do |s| s.add_dependency "ohai", ">= 0.5.0" s.add_dependency "bunny", ">= 0.6.0" - %w{json erubis extlib moneta}.each { |gem| s.add_dependency gem } + s.add_dependency "json", "<= 1.4.2" + %w{erubis extlib moneta}.each { |gem| s.add_dependency gem } s.bindir = "bin" s.executables = %w( chef-client chef-solo knife shef ) diff --git a/chef/bin/chef-client b/chef/bin/chef-client index bd090cc283..bfd5544319 100755 --- a/chef/bin/chef-client +++ b/chef/bin/chef-client @@ -18,9 +18,9 @@ # See the License for the specific language governing permissions and # limitations under the License. -$:.unshift(File.join(File.dirname(__FILE__), "..", "lib")) - require 'rubygems' +$:.unshift(File.join(File.dirname(__FILE__), "..", "lib")) +require 'chef' require 'chef/application/client' Chef::Application::Client.new.run diff --git a/chef/bin/chef-solo b/chef/bin/chef-solo index c5902fe085..69891acb73 100755 --- a/chef/bin/chef-solo +++ b/chef/bin/chef-solo @@ -18,9 +18,8 @@ # See the License for the specific language governing permissions and # limitations under the License. -$:.unshift(File.join(File.dirname(__FILE__), "..", "lib")) - require 'rubygems' +$:.unshift(File.join(File.dirname(__FILE__), "..", "lib")) require 'chef/application/solo' Chef::Application::Solo.new.run diff --git a/chef/bin/knife b/chef/bin/knife index a02c860522..1e2769906c 100755 --- a/chef/bin/knife +++ b/chef/bin/knife @@ -18,9 +18,8 @@ # 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' +$:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", "lib"))) require 'chef/application/knife' Chef::Application::Knife.new.run diff --git a/chef/bin/shef b/chef/bin/shef index 3d215488d6..2240e49067 100755 --- a/chef/bin/shef +++ b/chef/bin/shef @@ -26,11 +26,8 @@ require "irb" require "irb/completion" # TODO::EVIL -begin - require "#{File.dirname(__FILE__)}/../lib/chef" -rescue LoadError - # the bin got moved, e.g., by rubygems -end +$:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "..", "lib"))) + require "chef/shef" # dirty hack to make IRB initialize shef diff --git a/chef/distro/suse/etc/init.d/chef-client b/chef/distro/suse/etc/init.d/chef-client deleted file mode 100755 index 5c42230c12..0000000000 --- a/chef/distro/suse/etc/init.d/chef-client +++ /dev/null @@ -1,121 +0,0 @@ -#! /bin/sh -# Copyright (c) 1995-2003 SuSE Linux AG, Nuernberg, Germany. -# All rights reserved. -# -# Author: Mario Giammarco -# -### BEGIN INIT INFO -# Provides: chef-client -# Required-Start: $remote_fs $syslog $named -# Required-Stop: $remote_fs $syslog -# Default-Start: 3 5 -# Default-Stop: 0 1 2 6 -# Short-Description: Chef client -# Description: Starts chef client -### END INIT INFO - -# First reset status of this service -. /etc/rc.status -rc_reset - -# Return values acc. to LSB for all commands but status: -# 0 - success -# 1 - generic or unspecified error -# 2 - invalid or excess argument(s) -# 3 - unimplemented feature (e.g. "reload") -# 4 - insufficient privilege -# 5 - program is not installed -# 6 - program is not configured -# 7 - program is not running - -# set default options -CHEF_CONF="/etc/chef/client.rb" -if [ ! -f ${CHEF_CONF} ]; then - echo -n "Chef client configuration file, ${CHEF_CONF} does not exist." - # Tell the user this has skipped - rc_status -s - exit 6 -fi - -CHEF_BIN="/usr/bin/chef-client" -if [ ! -x ${CHEF_BIN} ]; then - echo -n "Chef client, ${CHEF_BIN} not installed!" - rc_status -s - exit 5 -fi - -CHEF_LOGFILE=/var/log/chef/chef-client.log -CHEF_OPTIONS="" - -# set default PID variables -CHEF_PID="/var/run/chef/chef-client.pid" -CHEF_PID_NOPREFIX="/var/run/chef/chef-client.pid" - -function chef_is_running() { - $0 status >/dev/null -} - -case "$1" in - start) - - echo -n "Starting chef-client" - - startproc $CHEF_BIN -d -c "$CHEF_CONF" -L "$CHEF_LOGFILE" "$CHEF_OPTIONS" ">/dev/null" - - rc_status -v - ;; - stop) - echo -n "Shutting down chef client" - killproc -p ${CHEF_PID} -TERM $CHEF_BIN - rc_status -v - rm -f ${CHEF_PID} 2>/dev/null - ;; - try-restart) - $0 status - if test $? = 0; then - $0 restart $2 - else - rc_reset # Not running is not a failure. - fi - # Remember status and be quiet - rc_status - ;; - restart) - $0 stop - $0 start $2 - rc_status - ;; - try-restart-iburst) - $0 status - if test $? = 0; then - $0 stop - $0 start iburst - else - rc_reset # Not running is not a failure. - fi - # Remember status and be quiet - rc_status - ;; - force-reload) - # Does not support signalling to reload - $0 try-restart - rc_status - ;; - reload) - echo -n "Reload chef-client" - # Does not support signalling to reload - rc_failed 3 - rc_status -v - ;; - status) - checkproc -p ${CHEF_PID} $CHEF_BIN - echo -n "Checking for chef-client " - checkproc -p ${CHEF_PID} $CHEF_BIN - rc_status -v - ;; - *) - echo "Usage: $0 {start|stop|status|try-restart|restart|try-restart-iburst|force-reload|reload|addserver|ntptimeset}" - exit 1 - ;; -esac -rc_exit diff --git a/chef/lib/chef.rb b/chef/lib/chef.rb index 0c494ef594..937e21acc9 100644 --- a/chef/lib/chef.rb +++ b/chef/lib/chef.rb @@ -16,22 +16,25 @@ # limitations under the License. # -$:.unshift(File.dirname(__FILE__)) unless - $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__))) - -require 'rubygems' require 'extlib' require 'chef/exceptions' require 'chef/log' require 'chef/config' -Dir[File.join(File.dirname(__FILE__), 'chef/mixin/**/*.rb')].sort.each { |lib| require lib } +require 'chef/providers' +require 'chef/resources' + +require 'chef/compile' +require 'chef/daemon' +require 'chef/runner' +require 'chef/webui_user' +require 'chef/openid_registration' class Chef - VERSION = "0.8.11" + VERSION = "0.8.12" end # Adds a Dir.glob to Ruby 1.8.5, for compat -if RUBY_VERSION < "1.8.6" +if RUBY_VERSION < "1.8.6" || RUBY_PLATFORM =~ /mswin|mingw32|windows/ class Dir class << self alias_method :glob_, :glob @@ -41,6 +44,7 @@ if RUBY_VERSION < "1.8.6" pattern.is_a? Array and !pattern.empty? ) or pattern.is_a? String ) + pattern.gsub!(/\\/, "/") if RUBY_PLATFORM =~ /mswin|mingw32|windows/ [pattern].flatten.inject([]) { |r, p| r + glob_(p, flags) } end alias_method :[], :glob diff --git a/chef/lib/chef/application.rb b/chef/lib/chef/application.rb index b992bc1c59..96825a8c01 100644 --- a/chef/lib/chef/application.rb +++ b/chef/lib/chef/application.rb @@ -6,9 +6,9 @@ # 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. @@ -22,10 +22,10 @@ require 'mixlib/cli' class Chef::Application include Mixlib::CLI - - def initialize + + def initialize super - + trap("TERM") do Chef::Application.fatal!("SIGTERM received, stopping", 1) end @@ -33,23 +33,28 @@ class Chef::Application trap("INT") do Chef::Application.fatal!("SIGINT received, stopping", 2) end - - trap("HUP") do - Chef::Log.info("SIGHUP received, reconfiguring") - reconfigure + + unless RUBY_PLATFORM =~ /mswin|mingw32|windows/ + trap("HUP") do + Chef::Log.info("SIGHUP received, reconfiguring") + reconfigure + end end - + at_exit do - # tear down the logger - end + # tear down the logger + end + + # Always switch to a readable directory. Keeps subsequent Dir.chdir() {} + # from failing due to permissions when launched as a less privileged user. end - + # Reconfigure the application. You'll want to override and super this method. def reconfigure configure_chef configure_logging end - + # Get this party started def run reconfigure @@ -60,25 +65,25 @@ class Chef::Application # Parse the configuration file def configure_chef parse_options - + Chef::Config.from_file(config[:config_file]) if !config[:config_file].nil? && File.exists?(config[:config_file]) && File.readable?(config[:config_file]) Chef::Config.merge!(config) end - + # Initialize and configure the logger def configure_logging Chef::Log.init(Chef::Config[:log_location]) Chef::Log.level = Chef::Config[:log_level] end - + # Called prior to starting the application, by the run method def setup_application - raise Chef::Exceptions::Application, "#{self.to_s}: you must override setup_application" + raise Chef::Exceptions::Application, "#{self.to_s}: you must override setup_application" end - + # Actually run the application def run_application - raise Chef::Exceptions::Application, "#{self.to_s}: you must override run_application" + raise Chef::Exceptions::Application, "#{self.to_s}: you must override run_application" end class << self diff --git a/chef/lib/chef/application/client.rb b/chef/lib/chef/application/client.rb index a6bd7a1003..6c265b84f6 100644 --- a/chef/lib/chef/application/client.rb +++ b/chef/lib/chef/application/client.rb @@ -113,12 +113,18 @@ class Chef::Application::Client < Chef::Application :description => "The chef server URL", :proc => nil - option :validation_token, - :short => "-t TOKEN", - :long => "--token TOKEN", - :description => "Set the openid validation token", - :proc => nil - + option :validation_key, + :short => "-K KEY_FILE", + :long => "--validation_key KEY_FILE", + :description => "Set the validation key file location, used for registering new clients", + :proc => nil + + option :client_key, + :short => "-k KEY_FILE", + :long => "--client_key KEY_FILE", + :description => "Set the client key file location", + :proc => nil + option :version, :short => "-v", :long => "--version", @@ -185,7 +191,6 @@ class Chef::Application::Client < Chef::Application @chef_client = Chef::Client.new @chef_client.json_attribs = @chef_client_json - @chef_client.validation_token = Chef::Config[:validation_token] @chef_client.node_name = Chef::Config[:node_name] end diff --git a/chef/lib/chef/application/knife.rb b/chef/lib/chef/application/knife.rb index df60da4040..c5ef8f4317 100644 --- a/chef/lib/chef/application/knife.rb +++ b/chef/lib/chef/application/knife.rb @@ -94,7 +94,13 @@ class Chef::Application::Knife < Chef::Application :short => "-p", :long => "--print-after", :description => "Show the data after a destructive operation" - + + option :format, + :short => "-f FORMAT", + :long => "--format FORMAT", + :description => "Which format to use for output", + :default => "json" + option :version, :short => "-v", :long => "--version", diff --git a/chef/lib/chef/application/solo.rb b/chef/lib/chef/application/solo.rb index 434638d607..8fa2b00592 100644 --- a/chef/lib/chef/application/solo.rb +++ b/chef/lib/chef/application/solo.rb @@ -158,7 +158,7 @@ class Chef::Application::Solo < Chef::Application end if Chef::Config[:recipe_url] - cookbooks_path = Chef::Config[:cookbook_path].detect{|e| e =~ /\/cookbooks\/*$/ } + cookbooks_path = Array(Chef::Config[:cookbook_path]).detect{|e| e =~ /\/cookbooks\/*$/ } recipes_path = File.expand_path(File.join(cookbooks_path, '..')) target_file = File.join(recipes_path, 'recipes.tgz') diff --git a/chef/lib/chef/applications.rb b/chef/lib/chef/applications.rb new file mode 100644 index 0000000000..48fd56acf4 --- /dev/null +++ b/chef/lib/chef/applications.rb @@ -0,0 +1,4 @@ +require 'chef/application/agent' +require 'chef/application/client' +require 'chef/application/knife' +require 'chef/application/solo' diff --git a/chef/lib/chef/cache/checksum.rb b/chef/lib/chef/cache/checksum.rb index 6effb3a303..e026d8875b 100644 --- a/chef/lib/chef/cache/checksum.rb +++ b/chef/lib/chef/cache/checksum.rb @@ -28,8 +28,9 @@ class Chef instance.checksum_for_file(*args) end - def checksum_for_file(file) - key, fstat = filename_to_key(file), File.stat(file) + def checksum_for_file(file, key=nil) + key ||= generate_key(file) + fstat = File.stat(file) lookup_checksum(key, fstat) || generate_checksum(key, file, fstat) end @@ -48,6 +49,10 @@ class Chef checksum end + def generate_key(file, group="chef") + "#{group}-file-#{file.gsub(/(#{File::SEPARATOR}|\.)/, '-')}" + end + private def file_unchanged?(cached, fstat) @@ -59,10 +64,6 @@ class Chef IO.foreach(file) {|line| digest.update(line) } digest.hexdigest end - - def filename_to_key(file) - "chef-file-#{file.gsub(/(#{File::SEPARATOR}|\.)/, '-')}" - end end end diff --git a/chef/lib/chef/certificate.rb b/chef/lib/chef/certificate.rb index 9e57c9ee89..b818b967af 100644 --- a/chef/lib/chef/certificate.rb +++ b/chef/lib/chef/certificate.rb @@ -128,11 +128,11 @@ class Chef return client_cert.public_key, client_keypair end - def gen_validation_key(name=Chef::Config[:validation_client_name], key_file=Chef::Config[:validation_key]) + def gen_validation_key(name=Chef::Config[:validation_client_name], key_file=Chef::Config[:validation_key], admin=false) # Create the validation key api_client = Chef::ApiClient.new api_client.name(name) - api_client.admin(true) + api_client.admin(admin) begin # If both the couch record and file exist, don't do anything. Otherwise, diff --git a/chef/lib/chef/client.rb b/chef/lib/chef/client.rb index 0ffe60e53a..08e2c76db0 100644 --- a/chef/lib/chef/client.rb +++ b/chef/lib/chef/client.rb @@ -37,12 +37,11 @@ class Chef include Chef::Mixin::GenerateURL include Chef::Mixin::Checksum - attr_accessor :node, :registration, :json_attribs, :validation_token, :node_name, :ohai, :rest, :runner, :compile + attr_accessor :node, :registration, :json_attribs, :node_name, :ohai, :rest, :compile # Creates a new Chef::Client. def initialize() @node = nil - @validation_token = nil @registration = nil @json_attribs = nil @node_name = nil @@ -90,7 +89,6 @@ class Chef build_node(@node_name) save_node sync_cookbooks - save_node converge save_node @@ -317,7 +315,7 @@ class Chef Chef::FileCache.list.each do |cache_file| if cache_file =~ /^cookbooks\/(.+?)\// unless cookbook_hash.has_key?($1) - Chef::Log.info("Removing #{cache_file} from the cache; it's cookbook is no longer needed on this client.") + Chef::Log.info("Removing #{cache_file} from the cache; its cookbook is no longer needed on this client.") Chef::FileCache.delete(cache_file) end end @@ -355,6 +353,22 @@ class Chef Chef::Log.debug("Compiling recipes for node #{@node_name}") unless solo Chef::Config[:cookbook_path] = File.join(Chef::Config[:file_cache_path], "cookbooks") + Chef::Log.warn("Node #{@node_name} has an empty run list.") if @node.run_list.empty? + else + # Check for cookbooks in the path given + # Chef::Config[:cookbook_path] can be a string or an array + # if it's an array, go through it and check each one, raise error at the last one if no files are found + Chef::Log.fatal "BUGBUG: cookbook_path: #{Chef::Config[:cookbook_path]}" + Array(Chef::Config[:cookbook_path]).each_with_index do |cookbook_path, index| + if directory_not_empty?(cookbook_path) + Chef::Log.fatal "BUGBUG: cb path not empty: #{cookbook_path}" + break + else + msg = "No cookbook found in #{Chef::Config[:cookbook_path].inspect}, make sure cookboook_path is set correctly." + Chef::Log.fatal(msg) + raise Chef::Exceptions::CookbookNotFound, msg if is_last_element?(index, Chef::Config[:cookbook_path]) + end + end end @compile = Chef::Compile.new(@node) @compile.go @@ -364,6 +378,16 @@ class Chef @runner.converge true end + + private + + def directory_not_empty?(path) + File.exists?(path) && (Dir.entries(path).size > 2) + end + + def is_last_element?(index, object) + object.kind_of?(Array) ? index == object.size - 1 : true + end end end diff --git a/chef/lib/chef/config.rb b/chef/lib/chef/config.rb index 3d90456486..6e573e7f3f 100644 --- a/chef/lib/chef/config.rb +++ b/chef/lib/chef/config.rb @@ -143,15 +143,14 @@ class Chef search_url "http://localhost:4000" solo false splay nil - ssl_client_cert "" - ssl_client_key "" + ssl_client_cert nil + ssl_client_key nil ssl_verify_mode :verify_none ssl_ca_path nil ssl_ca_file nil template_url "http://localhost:4000" umask 0022 user nil - validation_token nil role_path "/var/chef/roles" role_url "http://localhost:4000" recipe_url nil diff --git a/chef/lib/chef/cookbook_loader.rb b/chef/lib/chef/cookbook_loader.rb index 4a1e053425..c8591501c6 100644 --- a/chef/lib/chef/cookbook_loader.rb +++ b/chef/lib/chef/cookbook_loader.rb @@ -114,7 +114,12 @@ class Chef @cookbook[cookbook].provider_files = cookbook_settings[cookbook][:provider_files].values @metadata[cookbook] = Chef::Cookbook::Metadata.new(@cookbook[cookbook]) cookbook_settings[cookbook][:metadata_files].each do |meta_json| - @metadata[cookbook].from_json(IO.read(meta_json)) + begin + @metadata[cookbook].from_json(IO.read(meta_json)) + rescue JSON::ParserError + puts "Couldn't parse JSON in " + meta_json + raise + end end end end diff --git a/chef/lib/chef/daemon.rb b/chef/lib/chef/daemon.rb index eecdeb9607..1b9db3ba2b 100644 --- a/chef/lib/chef/daemon.rb +++ b/chef/lib/chef/daemon.rb @@ -121,6 +121,8 @@ class Chef # Change process user/group to those specified in Chef::Config # def change_privilege + Dir.chdir("/") + if Chef::Config[:user] and Chef::Config[:group] Chef::Log.info("About to change privilege to #{Chef::Config[:user]}:#{Chef::Config[:group]}") _change_privilege(Chef::Config[:user], Chef::Config[:group]) diff --git a/chef/lib/chef/data_bag_item.rb b/chef/lib/chef/data_bag_item.rb index a18649c989..0d6b1e9696 100644 --- a/chef/lib/chef/data_bag_item.rb +++ b/chef/lib/chef/data_bag_item.rb @@ -22,7 +22,8 @@ require 'chef/config' require 'chef/mixin/params_validate' require 'chef/mixin/from_file' require 'chef/couchdb' -require 'chef/data_bag_item' +require 'chef/index_queue' +require 'chef/data_bag' require 'extlib' require 'json' diff --git a/chef/lib/chef/exceptions.rb b/chef/lib/chef/exceptions.rb index f0fb60f778..d14401d3ad 100644 --- a/chef/lib/chef/exceptions.rb +++ b/chef/lib/chef/exceptions.rb @@ -37,5 +37,10 @@ class Chef class CannotWritePrivateKey < RuntimeError; end class RoleNotFound < RuntimeError; end class ValidationFailed < ArgumentError; end + class InvalidPrivateKey < ArgumentError; end + class ConfigurationError < ArgumentError; end + class RedirectLimitExceeded < RuntimeError; end + class AmbiguousRunlistSpecification < ArgumentError; end + class CookbookNotFound < RuntimeError; end end end diff --git a/chef/lib/chef/file_cache.rb b/chef/lib/chef/file_cache.rb index 4e2446844a..18ecd66512 100644 --- a/chef/lib/chef/file_cache.rb +++ b/chef/lib/chef/file_cache.rb @@ -6,9 +6,9 @@ # 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. @@ -26,7 +26,7 @@ class Chef class << self include Chef::Mixin::ParamsValidate include Chef::Mixin::CreatePath - + # Write a file to the File Cache. # # === Parameters @@ -47,7 +47,7 @@ class Chef :contents => { :kind_of => String }, } ) - + file_path_array = File.split(path) file_name = file_path_array.pop cache_path = create_cache_path(File.join(file_path_array)) @@ -56,7 +56,7 @@ class Chef io.close true end - + # Move a file in to the cache. Useful with the REST raw file output. # # === Parameteres @@ -73,19 +73,19 @@ class Chef :path => { :kind_of => String }, } ) - + file_path_array = File.split(path) file_name = file_path_array.pop if File.exists?(file) && File.writable?(file) FileUtils.mv( - file, + file, File.join(create_cache_path(File.join(file_path_array), true), file_name) ) else raise RuntimeError, "Cannot move #{file} to #{path}!" end end - + # Read a file from the File Cache # # === Parameters @@ -116,7 +116,7 @@ class Chef cache_path end end - + # Delete a file from the File Cache # # === Parameters @@ -140,7 +140,7 @@ class Chef end true end - + # List all the files in the Cache # # === Returns @@ -149,19 +149,19 @@ class Chef keys = Array.new Dir[File.join(Chef::Config[:file_cache_path], '**', '*')].each do |f| if File.file?(f) - path = f.match("^#{Chef::Config[:file_cache_path]}\/(.+)")[1] + path = f.match("^#{Dir[Chef::Config[:file_cache_path]].first}\/(.+)")[1] keys << path end end keys end - + # Whether or not this file exists in the Cache # # === Parameters - # path:: The path to the file you want to check - is relative + # path:: The path to the file you want to check - is relative # to Chef::Config[:file_cache_path] - # + # # === Returns # True:: If the file exists # False:: If it does not @@ -181,25 +181,25 @@ class Chef false end end - + # Create a full path to a given file in the cache. By default, # also creates the path if it does not exist. # # === Parameters # path:: The path to create, relative to Chef::Config[:file_cache_path] # create_if_missing:: True by default - whether to create the path if it does not exist - # + # # === Returns # String:: The fully expanded path def create_cache_path(path, create_if_missing=true) cache_dir = File.expand_path(File.join(Chef::Config[:file_cache_path], path)) if create_if_missing - create_path(cache_dir) + create_path(cache_dir) else cache_dir end end - + end end end diff --git a/chef/lib/chef/index_queue/indexable.rb b/chef/lib/chef/index_queue/indexable.rb index 0654c6b06c..f027497f13 100644 --- a/chef/lib/chef/index_queue/indexable.rb +++ b/chef/lib/chef/index_queue/indexable.rb @@ -44,10 +44,12 @@ class Chef self.class.index_object_type || Mixin::ConvertToClassName.snake_case_basename(self.class.name) end - def with_indexer_metadata(with_metadata={}) + def with_indexer_metadata(indexer_metadata={}) # changing input param symbol keys to strings, as the keys in hash that goes to solr are expected to be strings [cb] - with_metadata.each do |key,value| - with_metadata[key.to_s] = with_metadata.delete(key) + # Ruby 1.9 hates you, cb [dan] + with_metadata = {} + indexer_metadata.each_key do |key| + with_metadata[key.to_s] = indexer_metadata[key] end with_metadata["type"] ||= self.index_object_type diff --git a/chef/lib/chef/knife.rb b/chef/lib/chef/knife.rb index 8aadb0bcd9..beaa7f8326 100644 --- a/chef/lib/chef/knife.rb +++ b/chef/lib/chef/knife.rb @@ -20,6 +20,8 @@ require 'mixlib/cli' require 'chef/mixin/convert_to_class_name' +require 'pp' + class Chef class Knife include Mixlib::CLI @@ -96,11 +98,17 @@ class Chef klass_instance end - def ask_question(q) - print q - a = STDIN.readline - a.chomp! - a + def ask_question(question, opts={}) + question = question + "[#{opts[:default]}] " if opts[:default] + + stdout.print question + a = stdin.readline.strip + + if opts[:default] + a.empty? ? opts[:default] : a + else + a + end end def configure_chef @@ -126,8 +134,24 @@ class Chef puts data end - def json_pretty_print(data) - puts JSON.pretty_generate(data) + def output(data) + case config[:format] + when "json", nil + puts JSON.pretty_generate(data) + when "yaml" + require 'yaml' + puts YAML::dump(data) + when "text" + # If you were looking for some attribute and there is only one match + # just dump the attribute value + if data.length == 1 and config[:attribute] + puts data.values[0] + else + pp data + end + else + raise ArgumentError, "Unknown output format #{config[:format]}" + end end def format_list_for_display(list) @@ -182,7 +206,7 @@ class Chef return true if config[:yes] print "#{question}? (Y/N) " - answer = STDIN.readline + answer = stdin.readline answer.chomp! case answer when "Y", "y" @@ -237,7 +261,7 @@ class Chef Chef::Log.info("Saved #{output}") - json_pretty_print(format_for_display(object)) if config[:print_after] + output(format_for_display(object)) if config[:print_after] end def create_object(object, pretty_name=nil, &block) @@ -253,7 +277,7 @@ class Chef Chef::Log.info("Created (or updated) #{pretty_name}") - json_pretty_print(output) if config[:print_after] + output(output) if config[:print_after] end def delete_object(klass, name, delete_name=nil, &block) @@ -266,7 +290,7 @@ class Chef object.destroy end - json_pretty_print(format_for_display(object)) if config[:print_after] + output(format_for_display(object)) if config[:print_after] obj_name = delete_name ? "#{delete_name}[#{name}]" : object Chef::Log.warn("Deleted #{obj_name}!") @@ -285,7 +309,7 @@ class Chef to_delete = object_list end - json_pretty_print(format_list_for_display(to_delete)) + output(format_list_for_display(to_delete)) confirm("Do you really want to delete the above items") @@ -295,10 +319,18 @@ class Chef else object.destroy end - json_pretty_print(format_for_display(object)) if config[:print_after] + output(format_for_display(object)) if config[:print_after] Chef::Log.warn("Deleted #{fancy_name} #{name}") end end + + def stdout + STDOUT + end + + def stdin + STDIN + end def rest @rest ||= Chef::REST.new(Chef::Config[:chef_server_url]) diff --git a/chef/lib/chef/knife/client_list.rb b/chef/lib/chef/knife/client_list.rb index 24c23124ca..c6f3ca5136 100644 --- a/chef/lib/chef/knife/client_list.rb +++ b/chef/lib/chef/knife/client_list.rb @@ -32,7 +32,7 @@ class Chef :description => "Show corresponding URIs" def run - json_pretty_print(format_list_for_display(Chef::ApiClient.list)) + output(format_list_for_display(Chef::ApiClient.list)) end end end diff --git a/chef/lib/chef/knife/client_show.rb b/chef/lib/chef/knife/client_show.rb index f0efa97f10..81204547b3 100644 --- a/chef/lib/chef/knife/client_show.rb +++ b/chef/lib/chef/knife/client_show.rb @@ -33,7 +33,7 @@ class Chef def run client = Chef::ApiClient.load(@name_args[0]) - json_pretty_print(format_for_display(client)) + output(format_for_display(client)) end end diff --git a/chef/lib/chef/knife/configure.rb b/chef/lib/chef/knife/configure.rb index a362ed4465..58acbeb832 100644 --- a/chef/lib/chef/knife/configure.rb +++ b/chef/lib/chef/knife/configure.rb @@ -6,9 +6,9 @@ # 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. @@ -21,6 +21,8 @@ require 'chef/knife' class Chef class Knife class Configure < Knife + attr_reader :chef_server, :new_client_name, :admin_client_name, :admin_client_key + attr_reader :chef_repo, :new_client_key, :validation_client_name, :validation_key banner "Sub-Command: configure (options)" @@ -37,58 +39,48 @@ class Chef def configure_chef # We are just faking out the system so that you can do this without a key specified - Chef::Config[:node_name] = 'woot' + Chef::Config[:node_name] = 'woot' super Chef::Config[:node_name] = nil end - def run - config[:config_file] ||= ask_question("Where should I put the config file? ") - if File.exists?(config[:config_file]) - confirm("Overwrite #{config[:config_file]}") - end + def run + ask_user_for_config_path Mixlib::Log::Formatter.show_time = false Chef::Log.init(STDOUT) Chef::Log.level(:info) - chef_config_path = File.dirname(config[:config_file]) - FileUtils.mkdir_p(File.dirname(config[:config_file])) + FileUtils.mkdir_p(chef_config_path) - chef_server = config[:chef_server_url] || ask_question("Your chef server URL? ") - opscode_user = config[:node_name] || ask_question("Your client user name? ") - opscode_key = config[:client_key] || File.join(chef_config_path, "#{opscode_user}.pem") - validation_user = config[:validation_client_name] || ask_question("Your validation client user name? ") - validation_key = config[:validation_key] || File.join(chef_config_path, "#{validation_user}.pem") - chef_repo = config[:repository] || ask_question("Path to a chef repository (or leave blank)? ") - + ask_user_for_config - File.open(config[:config_file], "w") do |f| - f.puts <<EOH + ::File.open(config[:config_file], "w") do |f| + f.puts <<-EOH log_level :info log_location STDOUT -node_name '#{opscode_user}' -client_key '#{opscode_key}' -validation_client_name '#{validation_user}' +node_name '#{new_client_name}' +client_key '#{new_client_key}' +validation_client_name '#{validation_client_name}' validation_key '#{validation_key}' -chef_server_url '#{chef_server}' +chef_server_url '#{chef_server}' cache_type 'BasicFile' cache_options( :path => '#{File.join(chef_config_path, "checksums")}' ) EOH - unless chef_repo == "" + unless chef_repo.empty? f.puts "cookbook_path [ '#{chef_repo}/cookbooks', '#{chef_repo}/site-cookbooks' ]" - end + end end if config[:initial] Chef::Log.warn("Creating initial API user...") Chef::Config[:chef_server_url] = chef_server - Chef::Config[:node_name] = 'chef-webui' - Chef::Config[:client_key] = '/etc/chef/webui.pem' + Chef::Config[:node_name] = admin_client_name + Chef::Config[:client_key] = admin_client_key client_create = Chef::Knife::ClientCreate.new - client_create.name_args = [ opscode_user ] + client_create.name_args = [ new_client_name ] client_create.config[:admin] = true - client_create.config[:file] = opscode_key + client_create.config[:file] = new_client_key client_create.config[:yes] = true client_create.config[:no_editor] = true client_create.run @@ -96,7 +88,7 @@ EOH Chef::Log.warn("*****") Chef::Log.warn("") Chef::Log.warn("You must place your client key in:") - Chef::Log.warn(" #{opscode_key}") + Chef::Log.warn(" #{new_client_key}") Chef::Log.warn("Before running commands with Knife!") Chef::Log.warn("") Chef::Log.warn("*****") @@ -111,13 +103,32 @@ EOH Chef::Log.warn("Configuration file written to #{config[:config_file]}") end - end - end -end - - - + def ask_user_for_config_path + config[:config_file] ||= ask_question("Where should I put the config file? ") + if File.exists?(config[:config_file]) + confirm("Overwrite #{config[:config_file]}") + end + end + def ask_user_for_config + @chef_server = config[:chef_server_url] || ask_question("Your chef server URL? ", :default => 'http://localhost:4000') + @new_client_name = config[:node_name] || ask_question("Select a user name for your new client: ", :default => Etc.getlogin) + @admin_client_name = config[:admin_client_name] || ask_question("Your existing admin client user name? ", :default => 'chef-webui') + @admin_client_key = config[:admin_client_key] || ask_question("The location of your existing admin key? ", :default => '/etc/chef/webui.pem') + @validation_client_name = config[:validation_client_name] || ask_question("Your validation client user name? ", :default => 'chef-validator') + @validation_key = config[:validation_key] || ask_question("The location of your validation key? ", :default => '/etc/chef/validation.pem') + @chef_repo = config[:repository] || ask_question("Path to a chef repository (or leave blank)? ") + @new_client_key = config[:client_key] || File.join(chef_config_path, "#{@new_client_name}.pem") + end + def config_file + config[:config_file] + end + def chef_config_path + File.dirname(config_file) + end + end + end +end diff --git a/chef/lib/chef/knife/cookbook_list.rb b/chef/lib/chef/knife/cookbook_list.rb index 1b19bfb7db..7e1599dd62 100644 --- a/chef/lib/chef/knife/cookbook_list.rb +++ b/chef/lib/chef/knife/cookbook_list.rb @@ -31,7 +31,7 @@ class Chef :description => "Show corresponding URIs" def run - json_pretty_print(format_list_for_display(rest.get_rest('cookbooks'))) + output(format_list_for_display(rest.get_rest('cookbooks'))) end end end diff --git a/chef/lib/chef/knife/cookbook_metadata.rb b/chef/lib/chef/knife/cookbook_metadata.rb index b821b5d94b..d8a52693d5 100644 --- a/chef/lib/chef/knife/cookbook_metadata.rb +++ b/chef/lib/chef/knife/cookbook_metadata.rb @@ -7,9 +7,9 @@ # 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. @@ -36,20 +36,20 @@ class Chef :long => "--all", :description => "Generate metadata for all cookbooks, rather than just a single cookbook" - def run + def run if config[:cookbook_path] Chef::Config[:cookbook_path] = config[:cookbook_path] else config[:cookbook_path] = Chef::Config[:cookbook_path] end - if config[:all] + if config[:all] cl = Chef::CookbookLoader.new cl.each do |cookbook| generate_metadata(cookbook.name.to_s) end else - generate_metadata(@name_args[0]) + generate_metadata(@name_args[0]) end end @@ -57,31 +57,26 @@ class Chef Chef::Log.info("Generating metadata for #{cookbook}") config[:cookbook_path].reverse.each do |path| file = File.expand_path(File.join(path, cookbook, 'metadata.rb')) - if File.exists?(file) - Chef::Log.info("Generating from #{file}") - md = Chef::Cookbook::Metadata.new - md.name(cookbook) - md.from_file(file) - json_file = File.join(File.dirname(file), 'metadata.json') - File.open(json_file, "w") do |f| - f.write(JSON.pretty_generate(md)) - end - generated = true - Chef::Log.info("Generated #{json_file}") - else - Chef::Log.debug("No #{file} found; skipping!") - end + generate_metadata_from_file(cookbook, file) end end + def generate_metadata_from_file(cookbook, file) + if File.exists?(file) + Chef::Log.info("Generating from #{file}") + md = Chef::Cookbook::Metadata.new + md.name(cookbook) + md.from_file(file) + json_file = File.join(File.dirname(file), 'metadata.json') + File.open(json_file, "w") do |f| + f.write(JSON.pretty_generate(md)) + end + generated = true + Chef::Log.info("Generated #{json_file}") + else + Chef::Log.debug("No #{file} found; skipping!") + end + end end end end - - - - - - - - diff --git a/chef/lib/chef/knife/cookbook_metadata_from_file.rb b/chef/lib/chef/knife/cookbook_metadata_from_file.rb new file mode 100644 index 0000000000..8da5773e61 --- /dev/null +++ b/chef/lib/chef/knife/cookbook_metadata_from_file.rb @@ -0,0 +1,40 @@ +# +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Author:: Matthew Kent (<mkent@magoazul.com>) +# Copyright:: Copyright (c) 2009 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 'chef/knife' + +class Chef + class Knife + class CookbookMetadataFromFile < Knife + + banner "Sub-Command: cookbook metadata from FILE (options)" + + def run + file = @name_args[0] + cookbook = File.basename(File.dirname(file)) + + @metadata = Chef::Knife::CookbookMetadata.new + @metadata.generate_metadata_from_file(cookbook, file) + end + + end + end +end diff --git a/chef/lib/chef/knife/cookbook_show.rb b/chef/lib/chef/knife/cookbook_show.rb index eaf23e65d3..ac255e8b13 100644 --- a/chef/lib/chef/knife/cookbook_show.rb +++ b/chef/lib/chef/knife/cookbook_show.rb @@ -52,9 +52,9 @@ class Chef pretty_print(result) when 2 # We are showing a specific part of the cookbook result = rest.get_rest("cookbooks/#{@name_args[0]}") - json_pretty_print(result[@name_args[1]]) + output(result[@name_args[1]]) when 1 # We are showing the whole cookbook data - json_pretty_print(rest.get_rest("cookbooks/#{@name_args[0]}")) + output(rest.get_rest("cookbooks/#{@name_args[0]}")) end end diff --git a/chef/lib/chef/knife/cookbook_site_list.rb b/chef/lib/chef/knife/cookbook_site_list.rb index 505c321f23..19d1eccbb4 100644 --- a/chef/lib/chef/knife/cookbook_site_list.rb +++ b/chef/lib/chef/knife/cookbook_site_list.rb @@ -29,8 +29,8 @@ class Chef :long => "--with-uri", :description => "Show corresponding URIs" - def run - json_pretty_print(format_list_for_display(get_cookbook_list)) + def run + output(format_list_for_display(get_cookbook_list)) end def get_cookbook_list(items=10, start=0, cookbook_collection={}) diff --git a/chef/lib/chef/knife/cookbook_site_search.rb b/chef/lib/chef/knife/cookbook_site_search.rb index 1853b94209..4c4b5cc100 100644 --- a/chef/lib/chef/knife/cookbook_site_search.rb +++ b/chef/lib/chef/knife/cookbook_site_search.rb @@ -23,8 +23,8 @@ class Chef banner "Sub-Command: cookbook site search QUERY (options)" - def run - json_pretty_print(search_cookbook(name_args[0])) + def run + output(search_cookbook(name_args[0])) end def search_cookbook(query, items=10, start=0, cookbook_collection={}) diff --git a/chef/lib/chef/knife/cookbook_site_show.rb b/chef/lib/chef/knife/cookbook_site_show.rb index 2f0bcff897..128ac7bf4c 100644 --- a/chef/lib/chef/knife/cookbook_site_show.rb +++ b/chef/lib/chef/knife/cookbook_site_show.rb @@ -30,7 +30,7 @@ class Chef when 2 cookbook_data = rest.get_rest("http://cookbooks.opscode.com/api/v1/cookbooks/#{@name_args[0]}/versions/#{name_args[1].gsub('.', '_')}") end - json_pretty_print(format_for_display(cookbook_data)) + output(format_for_display(cookbook_data)) end def get_cookbook_list(items=10, start=0, cookbook_collection={}) diff --git a/chef/lib/chef/knife/cookbook_test.rb b/chef/lib/chef/knife/cookbook_test.rb new file mode 100644 index 0000000000..380739e68c --- /dev/null +++ b/chef/lib/chef/knife/cookbook_test.rb @@ -0,0 +1,103 @@ +# +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Author:: Matthew Kent (<mkent@magoazul.com>) +# Copyright:: Copyright (c) 2009 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 'chef/knife' +require 'chef/cache/checksum' + +class Chef + class Knife + class CookbookTest < Knife + + banner "Sub-Command: cookbook test [COOKBOOKS...] (options)" + + option :cookbook_path, + :short => "-o PATH:PATH", + :long => "--cookbook-path PATH:PATH", + :description => "A colon-separated path to look for cookbooks in", + :proc => lambda { |o| o.split(":") } + + option :all, + :short => "-a", + :long => "--all", + :description => "Test all cookbooks, rather than just a single cookbook" + + def run + if config[:cookbook_path] + Chef::Config[:cookbook_path] = config[:cookbook_path] + else + config[:cookbook_path] = Chef::Config[:cookbook_path] + end + + if config[:all] + cl = Chef::CookbookLoader.new + cl.each do |cookbook| + test_cookbook(cookbook.name.to_s) + end + else + @name_args.each do |cb| + test_cookbook(cb) + end + end + end + + def test_cookbook(cookbook) + Chef::Log.info("Running syntax check on #{cookbook}") + Array(config[:cookbook_path]).reverse.each do |path| + cookbook_dir = File.expand_path(File.join(path, cookbook)) + test_ruby(cookbook_dir) + test_templates(cookbook_dir) + end + end + + def test_ruby(cookbook_dir) + cache = Chef::Cache::Checksum.instance + Dir[File.join(cookbook_dir, '**', '*.rb')].each do |ruby_file| + key = cache.generate_key(ruby_file, "chef-test") + fstat = File.stat(ruby_file) + + if cache.lookup_checksum(key, fstat) + Chef::Log.info("No change in checksum of #{ruby_file}") + else + Chef::Log.info("Testing #{ruby_file} for syntax errors...") + Chef::Mixin::Command.run_command(:command => "ruby -c #{ruby_file}", :output_on_failure => true) + cache.generate_checksum(key, ruby_file, fstat) + end + end + end + + def test_templates(cookbook_dir) + cache = Chef::Cache::Checksum.instance + Dir[File.join(cookbook_dir, '**', '*.erb')].each do |erb_file| + key = cache.generate_key(erb_file, "chef-test") + fstat = File.stat(erb_file) + + if cache.lookup_checksum(key, fstat) + Chef::Log.info("No change in checksum of #{erb_file}") + else + Chef::Log.info("Testing template #{erb_file} for syntax errors...") + Chef::Mixin::Command.run_command(:command => "sh -c 'erubis -x #{erb_file} | ruby -c'", :output_on_failure => true) + cache.generate_checksum(key, erb_file, fstat) + end + end + end + + end + end +end diff --git a/chef/lib/chef/knife/cookbook_upload.rb b/chef/lib/chef/knife/cookbook_upload.rb index 69b61d71bd..a289394ae3 100644 --- a/chef/lib/chef/knife/cookbook_upload.rb +++ b/chef/lib/chef/knife/cookbook_upload.rb @@ -7,9 +7,9 @@ # 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. @@ -19,7 +19,6 @@ require 'chef/knife' require 'chef/streaming_cookbook_uploader' -require 'chef/cache/checksum' class Chef class Knife @@ -38,14 +37,16 @@ class Chef :long => "--all", :description => "Upload all cookbooks, rather than just a single cookbook" - def run + def run + Chef::Log.debug "Uploading cookbooks from #{config[:cookbook_path]}" + if config[:cookbook_path] Chef::Config[:cookbook_path] = config[:cookbook_path] else config[:cookbook_path] = Chef::Config[:cookbook_path] end - if config[:all] + if config[:all] cl = Chef::CookbookLoader.new cl.each do |cookbook| Chef::Log.info("** #{cookbook.name.to_s} **") @@ -58,28 +59,21 @@ class Chef end end end - - def test_ruby(cookbook_dir) - Dir[File.join(cookbook_dir, '**', '*.rb')].each do |ruby_file| - Chef::Log.info("Testing #{ruby_file} for syntax errors...") - Chef::Mixin::Command.run_command(:command => "ruby -c #{ruby_file}") - end - end - - def test_templates(cookbook_dir) - Dir[File.join(cookbook_dir, '**', '*.erb')].each do |erb_file| - Chef::Log.info("Testing template #{erb_file} for syntax errors...") - Chef::Mixin::Command.run_command(:command => "sh -c 'erubis -x #{erb_file} | ruby -c'") - end - end def upload_cookbook(cookbook_name) + # Syntax check all cookbook paths rather than tmp_cookbook_dir as to + # take advantage of the existing cache used/generated by knife cookbook + # test. + check = Chef::Knife::CookbookTest.new + check.config[:cookbook_path] = config[:cookbook_path] + check.name_args = [ cookbook_name ] + check.run if cookbook_name =~ /^#{File::SEPARATOR}/ - child_folders = cookbook_name + child_folders = cookbook_name cookbook_name = File.basename(cookbook_name) else - child_folders = config[:cookbook_path].inject([]) do |r, e| + child_folders = config[:cookbook_path].inject([]) do |r, e| r << File.join(e, cookbook_name) r end @@ -98,26 +92,23 @@ class Chef Chef::Log.debug("Staging at #{tmp_cookbook_dir}") - found_cookbook = false + found_cookbook = false child_folders.each do |file_path| if File.directory?(file_path) - found_cookbook = true + found_cookbook = true Chef::Log.info("Copying from #{file_path} to #{tmp_cookbook_dir}") - FileUtils.cp_r(file_path, tmp_cookbook_dir, :remove_destination => true) + FileUtils.cp_r(file_path, tmp_cookbook_dir, :remove_destination => true, :preserve => true) else Chef::Log.info("Nothing to copy from #{file_path}") end - end + end unless found_cookbook Chef::Log.fatal("Could not find cookbook #{cookbook_name}!") exit 17 end - test_ruby(tmp_cookbook_dir) - test_templates(tmp_cookbook_dir) - # First, generate metadata kcm = Chef::Knife::CookbookMetadata.new kcm.config[:cookbook_path] = [ tmp_cookbook_dir ] @@ -144,32 +135,32 @@ class Chef if cookbook_uploaded Chef::StreamingCookbookUploader.put( - "#{Chef::Config[:chef_server_url]}/cookbooks/#{cookbook_name}/_content", - Chef::Config[:node_name], - Chef::Config[:client_key], + "#{Chef::Config[:chef_server_url]}/cookbooks/#{cookbook_name}/_content", + Chef::Config[:node_name], + Chef::Config[:client_key], { - :file => File.new(tarball_name), + :file => File.new(tarball_name), :name => cookbook_name } ) else Chef::StreamingCookbookUploader.post( - "#{Chef::Config[:chef_server_url]}/cookbooks", - Chef::Config[:node_name], - Chef::Config[:client_key], + "#{Chef::Config[:chef_server_url]}/cookbooks", + Chef::Config[:node_name], + Chef::Config[:client_key], { - :file => File.new(tarball_name), + :file => File.new(tarball_name), :name => cookbook_name } ) end Chef::Log.info("Upload complete!") Chef::Log.debug("Removing local tarball at #{tarball_name}") - FileUtils.rm_rf tarball_name + FileUtils.rm_rf tarball_name Chef::Log.debug("Removing local staging directory at #{tmp_cookbook_dir}") FileUtils.rm_rf tmp_cookbook_dir end - + end end end diff --git a/chef/lib/chef/knife/data_bag_edit.rb b/chef/lib/chef/knife/data_bag_edit.rb index 16ac98dcad..50c53cc7a0 100644 --- a/chef/lib/chef/knife/data_bag_edit.rb +++ b/chef/lib/chef/knife/data_bag_edit.rb @@ -38,7 +38,7 @@ class Chef Chef::Log.info("Saved data_bag_item[#{@name_args[1]}]") - json_pretty_print(format_for_display(object)) if config[:print_after] + output(format_for_display(object)) if config[:print_after] end end end diff --git a/chef/lib/chef/knife/data_bag_list.rb b/chef/lib/chef/knife/data_bag_list.rb index 42a01be1be..30ebbe8a89 100644 --- a/chef/lib/chef/knife/data_bag_list.rb +++ b/chef/lib/chef/knife/data_bag_list.rb @@ -30,8 +30,8 @@ class Chef :long => "--with-uri", :description => "Show corresponding URIs" - def run - json_pretty_print(format_list_for_display(Chef::DataBag.list)) + def run + output(format_list_for_display(Chef::DataBag.list)) end end end diff --git a/chef/lib/chef/knife/data_bag_show.rb b/chef/lib/chef/knife/data_bag_show.rb index 977d2fcfd8..a030e56c85 100644 --- a/chef/lib/chef/knife/data_bag_show.rb +++ b/chef/lib/chef/knife/data_bag_show.rb @@ -32,7 +32,7 @@ class Chef else format_list_for_display(Chef::DataBag.load(@name_args[0])) end - json_pretty_print(display) + output(display) end end end diff --git a/chef/lib/chef/knife/ec2_instance_data.rb b/chef/lib/chef/knife/ec2_instance_data.rb index 82acda9295..00282e7765 100644 --- a/chef/lib/chef/knife/ec2_instance_data.rb +++ b/chef/lib/chef/knife/ec2_instance_data.rb @@ -38,7 +38,7 @@ class Chef "attributes" => { "run_list" => @name_args } } data = edit_data(data) if config[:edit] - json_pretty_print(data) + output(data) end end end diff --git a/chef/lib/chef/knife/index_rebuild.rb b/chef/lib/chef/knife/index_rebuild.rb index 4f99c3c932..b5d982be98 100644 --- a/chef/lib/chef/knife/index_rebuild.rb +++ b/chef/lib/chef/knife/index_rebuild.rb @@ -32,7 +32,7 @@ class Chef def run nag - json_pretty_print rest.post_rest("/search/reindex", {}) + output rest.post_rest("/search/reindex", {}) end def nag @@ -48,4 +48,4 @@ class Chef end end -end
\ No newline at end of file +end diff --git a/chef/lib/chef/knife/node_from_file.rb b/chef/lib/chef/knife/node_from_file.rb index 02e33158a2..b0b5ff713c 100644 --- a/chef/lib/chef/knife/node_from_file.rb +++ b/chef/lib/chef/knife/node_from_file.rb @@ -31,7 +31,7 @@ class Chef updated.save - json_pretty_print(format_for_display(updated)) if config[:print_after] + output(format_for_display(updated)) if config[:print_after] Chef::Log.warn("Updated Node #{updated.name}!") end diff --git a/chef/lib/chef/knife/node_list.rb b/chef/lib/chef/knife/node_list.rb index 5239d2c29d..ad4255a938 100644 --- a/chef/lib/chef/knife/node_list.rb +++ b/chef/lib/chef/knife/node_list.rb @@ -31,8 +31,8 @@ class Chef :long => "--with-uri", :description => "Show corresponding URIs" - def run - json_pretty_print(format_list_for_display(Chef::Node.list)) + def run + output(format_list_for_display(Chef::Node.list)) 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 index 33f3e81917..5ce61b12a8 100644 --- a/chef/lib/chef/knife/node_run_list_add.rb +++ b/chef/lib/chef/knife/node_run_list_add.rb @@ -41,7 +41,7 @@ class Chef config[:run_list] = true - json_pretty_print(format_for_display(node)) + output(format_for_display(node)) end def add_to_run_list(node, new_value, after=nil) diff --git a/chef/lib/chef/knife/node_run_list_remove.rb b/chef/lib/chef/knife/node_run_list_remove.rb index ab90015128..6bda2c21de 100644 --- a/chef/lib/chef/knife/node_run_list_remove.rb +++ b/chef/lib/chef/knife/node_run_list_remove.rb @@ -36,7 +36,7 @@ class Chef config[:run_list] = true - json_pretty_print(format_for_display(node)) + output(format_for_display(node)) end end diff --git a/chef/lib/chef/knife/node_show.rb b/chef/lib/chef/knife/node_show.rb index 1c7d29a037..e5cede73d1 100644 --- a/chef/lib/chef/knife/node_show.rb +++ b/chef/lib/chef/knife/node_show.rb @@ -38,7 +38,7 @@ class Chef def run node = Chef::Node.load(@name_args[0]) - json_pretty_print(format_for_display(node)) + output(format_for_display(node)) end end end diff --git a/chef/lib/chef/knife/role_from_file.rb b/chef/lib/chef/knife/role_from_file.rb index 0ec9736c1c..b366e7f7f6 100644 --- a/chef/lib/chef/knife/role_from_file.rb +++ b/chef/lib/chef/knife/role_from_file.rb @@ -31,7 +31,7 @@ class Chef updated.save - json_pretty_print(format_for_display(updated)) if config[:print_after] + output(format_for_display(updated)) if config[:print_after] Chef::Log.warn("Updated Role #{updated.name}!") end diff --git a/chef/lib/chef/knife/role_list.rb b/chef/lib/chef/knife/role_list.rb index 6f7e8df55f..a8e127246b 100644 --- a/chef/lib/chef/knife/role_list.rb +++ b/chef/lib/chef/knife/role_list.rb @@ -32,7 +32,7 @@ class Chef :description => "Show corresponding URIs" def run - json_pretty_print(format_list_for_display(Chef::Role.list)) + output(format_list_for_display(Chef::Role.list)) end end end diff --git a/chef/lib/chef/knife/role_show.rb b/chef/lib/chef/knife/role_show.rb index 0ed17209c4..b5e1f0f0a3 100644 --- a/chef/lib/chef/knife/role_show.rb +++ b/chef/lib/chef/knife/role_show.rb @@ -33,7 +33,7 @@ class Chef def run role = Chef::Role.load(@name_args[0]) - json_pretty_print(format_for_display(role)) + output(format_for_display(role)) end end diff --git a/chef/lib/chef/knife/search.rb b/chef/lib/chef/knife/search.rb index cede2f16c9..f9a291c36b 100644 --- a/chef/lib/chef/knife/search.rb +++ b/chef/lib/chef/knife/search.rb @@ -82,7 +82,7 @@ class Chef puts display[:rows].join("\n") end else - json_pretty_print(display) + output(display) end end end diff --git a/chef/lib/chef/knife/terremark_server_create.rb b/chef/lib/chef/knife/terremark_server_create.rb new file mode 100644 index 0000000000..87e933db91 --- /dev/null +++ b/chef/lib/chef/knife/terremark_server_create.rb @@ -0,0 +1,152 @@ +# +# 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 'json' +require 'tempfile' + +class Chef + class Knife + class TerremarkServerCreate < Knife + + banner "Sub-Command: terremark server create NAME [RUN LIST...] (options)" + + option :terremark_password, + :short => "-K PASSWORD", + :long => "--terremark-password PASSWORD", + :description => "Your terremark password", + :proc => Proc.new { |key| Chef::Config[:knife][:terremark_password] = key } + + option :terremark_username, + :short => "-A USERNAME", + :long => "--terremark-username USERNAME", + :description => "Your terremark username", + :proc => Proc.new { |username| Chef::Config[:knife][:terremark_username] = username } + + option :terremark_service, + :short => "-S SERVICE", + :long => "--terremark-service SERVICE", + :description => "Your terremark service name", + :proc => Proc.new { |service| Chef::Config[:knife][:terremark_service] = service } + + def h + @highline ||= HighLine.new + end + + def run + require 'fog' + require 'highline' + require 'net/ssh/multi' + require 'readline' + require 'net/scp' + + server_name = @name_args[0] + + terremark = Fog::Terremark.new( + :terremark_username => Chef::Config[:knife][:terremark_username], + :terremark_password => Chef::Config[:knife][:terremark_password], + :terremark_service => Chef::Config[:knife][:terremark_service] || :vcloud + ) + + $stdout.sync = true + + puts "Instantiating vApp #{h.color(server_name, :bold)}" + vapp_id = terremark.instantiate_vapp_template(server_name).body['href'].split('/').last + + deploy_task_id = terremark.deploy_vapp(vapp_id).body['href'].split('/').last + print "Waiting for deploy task [#{h.color(deploy_task_id, :bold)}]" + terremark.tasks.get(deploy_task_id).wait_for { print "."; ready? } + print "\n" + + power_on_task_id = terremark.power_on(vapp_id).body['href'].split('/').last + print "Waiting for power on task [#{h.color(power_on_task_id, :bold)}]" + terremark.tasks.get(power_on_task_id).wait_for { print "."; ready? } + print "\n" + + private_ip = terremark.get_vapp(vapp_id).body['IpAddress'] + ssh_internet_service = terremark.create_internet_service(terremark.default_vdc_id, 'SSH', 'TCP', 22).body + ssh_internet_service_id = ssh_internet_service['Id'] + public_ip = ssh_internet_service['PublicIpAddress']['Name'] + public_ip_id = ssh_internet_service['PublicIpAddress']['Id'] + ssh_node_service_id = terremark.add_node_service(ssh_internet_service_id, private_ip, 'SSH', 22).body['Id'] + + puts "\nBootstrapping #{h.color(server_name, :bold)}..." + password = terremark.get_vapp_template(12).body['Description'].scan(/\npassword: (.*)\n/).first.first + + command = <<EOH +bash -c ' +echo nameserver 208.67.222.222 > /etc/resolv.conf +echo nameserver 208.67.220.220 >> /etc/resolv.conf + +if [ ! -f /usr/bin/chef-client ]; then + apt-get update + apt-get install -y ruby ruby1.8-dev build-essential wget libruby-extras libruby1.8-extras + cd /tmp + wget http://rubyforge.org/frs/download.php/69365/rubygems-1.3.6.tgz + tar xvf rubygems-1.3.6.tgz + cd rubygems-1.3.6 + ruby setup.rb + cp /usr/bin/gem1.8 /usr/bin/gem + gem install chef ohai --no-rdoc --no-ri --verbose +fi + +mkdir -p /etc/chef + +( +cat <<'EOP' +#{IO.read(Chef::Config[:validation_key])} +EOP +) > /etc/chef/validation.pem + +( +cat <<'EOP' +log_level :info +log_location STDOUT +chef_server_url "#{Chef::Config[:chef_server_url]}" +validation_client_name "#{Chef::Config[:validation_client_name]}" +EOP +) > /etc/chef/client.rb + +( +cat <<'EOP' +#{{ "run_list" => @name_args[1..-1] }.to_json} +EOP +) > /etc/chef/first-boot.json + +/usr/bin/chef-client -j /etc/chef/first-boot.json' +EOH + + begin + ssh = Chef::Knife::Ssh.new + ssh.name_args = [ public_ip, "sudo #{command}" ] + ssh.config[:ssh_user] = "vcloud" + ssh.config[:manual] = true + ssh.config[:password] = password + ssh.password = password + ssh.run + rescue Errno::ETIMEDOUT + puts "Timed out on bootstrap, re-trying. Hit CTRL-C to abort." + puts "You probably need to log in to Terremark and powercycle #{h.color(@name_args[0], :bold)}" + retry + end + + end + end + end +end + diff --git a/chef/lib/chef/knife/terremark_server_delete.rb b/chef/lib/chef/knife/terremark_server_delete.rb new file mode 100644 index 0000000000..38f98dec4b --- /dev/null +++ b/chef/lib/chef/knife/terremark_server_delete.rb @@ -0,0 +1,87 @@ +# +# 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 'json' + +class Chef + class Knife + class TerremarkServerDelete < Knife + + banner "Sub-Command: terremark server delete SERVER (options)" + + def h + @highline ||= HighLine.new + end + + def run + require 'fog' + require 'highline' + + terremark = Fog::Terremark.new( + :terremark_username => Chef::Config[:knife][:terremark_username], + :terremark_password => Chef::Config[:knife][:terremark_password], + :terremark_service => Chef::Config[:knife][:terremark_service] || :vcloud + ) + + $stdout.sync = true + + vapp_id = terremark.servers.detect {|server| server.name == @name_args[0]}.id + confirm("Do you really want to delete server ID #{vapp_id} named #{@name_args[0]}") + + puts "Cleaning up internet services..." + private_ip = terremark.servers.get(vapp_id).ip_address + internet_services = terremark.get_internet_services(terremark.default_vdc_id).body['InternetServices'] + public_ip_usage = {} + internet_services.each do |internet_service| + public_ip_address = internet_service['PublicIpAddress']['Name'] + public_ip_usage[public_ip_address] ||= [] + public_ip_usage[public_ip_address] << internet_service['Id'] + end + internet_services.each do |internet_service| + node_services = terremark.get_node_services(internet_service['Id']).body['NodeServices'] + node_services.delete_if do |node_service| + if node_service['IpAddress'] == private_ip + terremark.delete_node_service(node_service['Id']) + end + end + if node_services.empty? + terremark.delete_internet_service(internet_service['Id']) + public_ip_usage.each_value {|internet_services| internet_services.delete(internet_service['Id'])} + if public_ip_usage[internet_service['PublicIpAddress']['Name']].empty? + terremark.delete_public_ip(internet_service['PublicIpAddress']['Id']) + end + end + end + + power_off_task_id = terremark.power_off(vapp_id).body['href'].split('/').last + print "Waiting for power off task [#{h.color(power_off_task_id, :bold)}]" + terremark.tasks.get(power_off_task_id).wait_for { print '.'; ready? } + print "\n" + + print "Deleting vApp #{h.color(vapp_id, :bold)}" + delete_vapp_task_id = terremark.delete_vapp(vapp_id).headers['Location'].split('/').last + terremark.tasks.get(delete_vapp_task_id).wait_for { print '.'; ready? } + print "\n" + + Chef::Log.warn("Deleted server #{@name_args[0]}") + end + end + end +end + diff --git a/chef/lib/chef/knife/terremark_server_list.rb b/chef/lib/chef/knife/terremark_server_list.rb new file mode 100644 index 0000000000..3aba858570 --- /dev/null +++ b/chef/lib/chef/knife/terremark_server_list.rb @@ -0,0 +1,77 @@ +# +# 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 'json' +require 'tempfile' + +class Chef + class Knife + class TerremarkServerList < Knife + + banner "Sub-Command: terremark server list (options)" + + option :terremark_password, + :short => "-K PASSWORD", + :long => "--terremark-password PASSWORD", + :description => "Your terremark password", + :proc => Proc.new { |key| Chef::Config[:knife][:terremark_password] = key } + + option :terremark_username, + :short => "-A USERNAME", + :long => "--terremark-username USERNAME", + :description => "Your terremark username", + :proc => Proc.new { |username| Chef::Config[:knife][:terremark_username] = username } + + option :terremark_service, + :short => "-S SERVICE", + :long => "--terremark-service SERVICE", + :description => "Your terremark service name", + :proc => Proc.new { |service| Chef::Config[:knife][:terremark_service] = service } + + def h + @highline ||= HighLine.new + end + + def run + require 'fog' + require 'highline' + + server_name = @name_args[0] + + terremark = Fog::Terremark.new( + :terremark_username => Chef::Config[:knife][:terremark_username], + :terremark_password => Chef::Config[:knife][:terremark_password], + :terremark_service => Chef::Config[:knife][:terremark_service] || :vcloud + ) + + $stdout.sync = true + + server_list = [ h.color('ID', :bold), h.color('Name', :bold) ] + terremark.servers.all.each do |server| + server_list << server.id.to_s + server_list << server.name + end + puts h.list(server_list, :columns_across, 2) + + end + end + end +end + + diff --git a/chef/lib/chef/mixin/command.rb b/chef/lib/chef/mixin/command.rb index eec0966189..f703674c03 100644 --- a/chef/lib/chef/mixin/command.rb +++ b/chef/lib/chef/mixin/command.rb @@ -97,6 +97,7 @@ class Chef # timeout<String>: How many seconds to wait for the command to execute before timing out # returns<String>: The single exit value command is expected to return, otherwise causes an exception # ignore_failure<Boolean>: Whether to raise an exception on failure, or just return the status + # output_on_failure<Boolean>: Return output in raised exception regardless of Log.level # # user<String>: The UID or user name of the user to execute the command as # group<String>: The GID or group name of the group to execute the command as @@ -108,6 +109,7 @@ class Chef command_output = "" args[:ignore_failure] ||= false + args[:output_on_failure] ||= false if args.has_key?(:creates) if File.exists?(args[:creates]) @@ -165,18 +167,18 @@ class Chef module_function :output_of_command - def handle_command_failures(status, command_output, args={}) - unless args[:ignore_failure] - args[:returns] ||= 0 - if status.exitstatus != args[:returns] + def handle_command_failures(status, command_output, opts={}) + unless opts[:ignore_failure] + opts[:returns] ||= 0 + unless Array(opts[:returns]).include?(status.exitstatus) # if the log level is not debug, through output of command when we fail output = "" - if Chef::Log.level == :debug - output << "\n---- Begin output of #{args[:command]} ----\n" - output << "#{command_output}" - output << "---- End output of #{args[:command]} ----\n" + if Chef::Log.level == :debug || opts[:output_on_failure] + output << "\n---- Begin output of #{opts[:command]} ----\n" + output << command_output.to_s + output << "\n---- End output of #{opts[:command]} ----\n" end - raise Chef::Exceptions::Exec, "#{args[:command]} returned #{status.exitstatus}, expected #{args[:returns]}#{output}" + raise Chef::Exceptions::Exec, "#{opts[:command]} returned #{status.exitstatus}, expected #{opts[:returns]}#{output}" end end end diff --git a/chef/lib/chef/mixin/recipe_definition_dsl_core.rb b/chef/lib/chef/mixin/recipe_definition_dsl_core.rb index 1f27d1bd79..c8afd07574 100644 --- a/chef/lib/chef/mixin/recipe_definition_dsl_core.rb +++ b/chef/lib/chef/mixin/recipe_definition_dsl_core.rb @@ -17,11 +17,14 @@ # limitations under the License. # -require 'chef/recipe' require 'chef/resource' require 'chef/mixin/convert_to_class_name' require 'chef/mixin/language' +# UGH. this is a circular require that will cause an uninitialized constant +# error, but this file really does depend on Chef::Recipe. oh well. +# require 'chef/recipe' + class Chef module Mixin module RecipeDefinitionDSLCore diff --git a/chef/lib/chef/mixin/template.rb b/chef/lib/chef/mixin/template.rb index 0309b5994c..df6c2839df 100644 --- a/chef/lib/chef/mixin/template.rb +++ b/chef/lib/chef/mixin/template.rb @@ -42,15 +42,16 @@ class Chef rescue Object => e raise TemplateError.new(e, template, context) end - final_tempfile = Tempfile.new("chef-rendered-template") - final_tempfile.print(output) - final_tempfile.close - final_tempfile + Tempfile.open("chef-rendered-template") do |tempfile| + tempfile.print(output) + tempfile.close + yield tempfile + end end class TemplateError < RuntimeError attr_reader :original_exception, :context - SOURCE_CONTEXT_WINDOW = 2 unless defined? SOURCE_CONTEXT_WINDOW + SOURCE_CONTEXT_WINDOW = 2 def initialize(original_exception, template, context) @original_exception, @template, @context = original_exception, template, context diff --git a/chef/lib/chef/mixin/xml_escape.rb b/chef/lib/chef/mixin/xml_escape.rb index c9ef7e76f9..7ab40470b4 100644 --- a/chef/lib/chef/mixin/xml_escape.rb +++ b/chef/lib/chef/mixin/xml_escape.rb @@ -80,18 +80,18 @@ class Chef 156 => 339, # latin small ligature oe 158 => 382, # latin small letter z with caron 159 => 376 # latin capital letter y with diaeresis - } unless defined?(CP1252) + } # http://www.w3.org/TR/REC-xml/#dt-chardata PREDEFINED = { 38 => '&', # ampersand 60 => '<', # left angle bracket 62 => '>' # right angle bracket - } unless defined?(PREDEFINED) + } # http://www.w3.org/TR/REC-xml/#charsets VALID = [[0x9, 0xA, 0xD], (0x20..0xD7FF), - (0xE000..0xFFFD), (0x10000..0x10FFFF)] unless defined?(VALID) + (0xE000..0xFFFD), (0x10000..0x10FFFF)] def xml_escape(unescaped_str) begin diff --git a/chef/lib/chef/mixins.rb b/chef/lib/chef/mixins.rb new file mode 100644 index 0000000000..5c9b8f4320 --- /dev/null +++ b/chef/lib/chef/mixins.rb @@ -0,0 +1,16 @@ +require 'chef/mixin/check_helper' +require 'chef/mixin/checksum' +require 'chef/mixin/command' +require 'chef/mixin/convert_to_class_name' +require 'chef/mixin/create_path' +require 'chef/mixin/deep_merge' +require 'chef/mixin/find_preferred_file' +require 'chef/mixin/from_file' +require 'chef/mixin/generate_url' +require 'chef/mixin/language' +require 'chef/mixin/language_include_attribute' +require 'chef/mixin/language_include_recipe' +require 'chef/mixin/params_validate' +require 'chef/mixin/recipe_definition_dsl_core' +require 'chef/mixin/template' +require 'chef/mixin/xml_escape'
\ No newline at end of file diff --git a/chef/lib/chef/node.rb b/chef/lib/chef/node.rb index f79dd4fc87..9bdf7228e6 100644 --- a/chef/lib/chef/node.rb +++ b/chef/lib/chef/node.rb @@ -7,9 +7,9 @@ # 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. @@ -22,6 +22,7 @@ require 'chef/mixin/check_helper' require 'chef/mixin/params_validate' require 'chef/mixin/from_file' require 'chef/mixin/language_include_attribute' +require 'chef/mixin/deep_merge' require 'chef/couchdb' require 'chef/rest' require 'chef/run_list' @@ -36,20 +37,20 @@ class Chef attr_accessor :recipe_list, :couchdb, :couchdb_rev, :run_state, :run_list, :override_attrs, :default_attrs, :normal_attrs, :automatic_attrs, :cookbook_loader attr_reader :node attr_reader :couchdb_id - + include Chef::Mixin::CheckHelper include Chef::Mixin::FromFile include Chef::Mixin::ParamsValidate include Chef::Mixin::LanguageIncludeAttribute include Chef::IndexQueue::Indexable - + DESIGN_DOCUMENT = { "version" => 9, "language" => "javascript", "views" => { "all" => { "map" => <<-EOJS - function(doc) { + function(doc) { if (doc.chef_type == "node") { emit(doc.name, doc); } @@ -58,7 +59,7 @@ class Chef }, "all_id" => { "map" => <<-EOJS - function(doc) { + function(doc) { if (doc.chef_type == "node") { emit(doc.name, doc.name); } @@ -84,7 +85,7 @@ class Chef to_emit["ohai_time"] = doc["attributes"]["ohai_time"]; } else { to_emit["ohai_time"] = "Undefined"; - } + } if (doc["attributes"]["uptime"]) { to_emit["uptime"] = doc["attributes"]["uptime"]; } else { @@ -125,7 +126,7 @@ class Chef } }, } - + # Create a new Chef::Node object. def initialize(couchdb=nil) @name = nil @@ -154,36 +155,36 @@ class Chef end def chef_server_rest - Chef::REST.new(Chef::Config[:chef_server_url]) + Chef::REST.new(Chef::Config[:chef_server_url]) end def self.chef_server_rest - Chef::REST.new(Chef::Config[:chef_server_url]) + Chef::REST.new(Chef::Config[:chef_server_url]) end - # Find a recipe for this Chef::Node by fqdn. Will search first for + # Find a recipe for this Chef::Node by fqdn. Will search first for # Chef::Config["node_path"]/fqdn.rb, then hostname.rb, then default.rb. - # + # # Returns a new Chef::Node object. # - # Raises an ArgumentError if it cannot find the node. + # Raises an ArgumentError if it cannot find the node. def find_file(fqdn) host_parts = fqdn.split(".") hostname = host_parts[0] - + [fqdn, hostname, "default"].each { |fname| - node_file = File.join(Chef::Config[:node_path], "#{fname.to_s}.rb") + node_file = File.join(Chef::Config[:node_path], "#{fname.to_s}.rb") return self.from_file(node_file) if File.exists?(node_file) } - - raise ArgumentError, "Cannot find a node matching #{fqdn}, not even with default.rb!" + + raise ArgumentError, "Cannot find a node matching #{fqdn}, not even with default.rb!" end - + # Set the name of this Node, or return the current name. def name(arg=nil) if arg != nil validate( - {:name => arg }, + {:name => arg }, {:name => { :kind_of => String, :cannot_be => :blank} }) @@ -194,7 +195,7 @@ class Chef end def attribute - Chef::Node::Attribute.new(@normal_attrs, @default_attrs, @override_attrs, @automatic_attrs) + attribute end def attribute=(value) @@ -203,14 +204,14 @@ class Chef # Return an attribute of this node. Returns nil if the attribute is not found. def [](attrib) - Chef::Node::Attribute.new(@normal_attrs, @default_attrs, @override_attrs, @automatic_attrs)[attrib] + attribute[attrib] end - + # Set an attribute of this node def []=(attrib, value) - Chef::Node::Attribute.new(@normal_attrs, @default_attrs, @override_attrs, @automatic_attrs)[attrib] = value + attribute[attrib] = value end - + def store(attrib, value) self[attrib] = value end @@ -218,7 +219,7 @@ class Chef # Set a normal attribute of this node, but auto-vivifiy any Mashes that # might be missing def normal - attrs = Chef::Node::Attribute.new(@normal_attrs, @default_attrs, @override_attrs, @automatic_attrs) + attrs = attribute attrs.set_type = :normal attrs.auto_vivifiy_on_read = true attrs @@ -229,7 +230,7 @@ class Chef # Set a normal attribute of this node, auto-vivifiying any mashes that are # missing, but if the final value already exists, don't set it def normal_unless - attrs = Chef::Node::Attribute.new(@normal_attrs, @default_attrs, @override_attrs, @automatic_attrs) + attrs = attribute attrs.set_type = :normal attrs.auto_vivifiy_on_read = true attrs.set_unless_value_present = true @@ -240,7 +241,7 @@ class Chef # Set a default of this node, but auto-vivifiy any Mashes that might # be missing def default - attrs = Chef::Node::Attribute.new(@normal_attrs, @default_attrs, @override_attrs, @automatic_attrs) + attrs = attribute attrs.set_type = :default attrs.auto_vivifiy_on_read = true attrs @@ -249,7 +250,7 @@ class Chef # Set a default attribute of this node, auto-vivifiying any mashes that are # missing, but if the final value already exists, don't set it def default_unless - attrs = Chef::Node::Attribute.new(@normal_attrs, @default_attrs, @override_attrs, @automatic_attrs) + attrs = attribute attrs.set_type = :default attrs.auto_vivifiy_on_read = true attrs.set_unless_value_present = true @@ -259,7 +260,7 @@ class Chef # Set an override attribute of this node, but auto-vivifiy any Mashes that # might be missing def override - attrs = Chef::Node::Attribute.new(@normal_attrs, @default_attrs, @override_attrs, @automatic_attrs) + attrs = attribute attrs.set_type = :override attrs.auto_vivifiy_on_read = true attrs @@ -268,7 +269,7 @@ class Chef # Set an override attribute of this node, auto-vivifiying any mashes that # are missing, but if the final value already exists, don't set it def override_unless - attrs = Chef::Node::Attribute.new(@normal_attrs, @default_attrs, @override_attrs, @automatic_attrs) + attrs = attribute attrs.set_type = :override attrs.auto_vivifiy_on_read = true attrs.set_unless_value_present = true @@ -281,44 +282,44 @@ class Chef # Only works on the top level. Preferred way is to use the normal [] style # lookup and call attribute?() def attribute?(attrib) - Chef::Node::Attribute.new(@normal_attrs, @default_attrs, @override_attrs, @automatic_attrs).attribute?(attrib) + attribute.attribute?(attrib) end - - # Yield each key of the top level to the block. + + # Yield each key of the top level to the block. def each(&block) - Chef::Node::Attribute.new(@normal_attrs, @default_attrs, @override_attrs, @automatic_attrs).each(&block) + attribute.each(&block) end - + # Iterates over each attribute, passing the attribute and value to the block. def each_attribute(&block) - Chef::Node::Attribute.new(@normal_attrs, @default_attrs, @override_attrs, @automatic_attrs).each_attribute(&block) + attribute.each_attribute(&block) end # Set an attribute based on the missing method. If you pass an argument, we'll use that # to set the attribute values. Otherwise, we'll wind up just returning the attributes # value. def method_missing(symbol, *args) - attrs = Chef::Node::Attribute.new(@normal_attrs, @default_attrs, @override_attrs, @automatic_attrs) + attrs = attribute attrs.set_type = :normal attrs.send(symbol, *args) end - + # Returns true if this Node expects a given recipe, false if not. def recipe?(recipe_name) @run_list.include?(recipe_name) || @run_state[:seen_recipes].include?(recipe_name) end - + # Returns true if this Node expects a given role, false if not. def role?(role_name) @run_list.include?("role[#{role_name}]") end # Returns an Array of roles and recipes, in the order they will be applied. - # If you call it with arguments, they will become the new list of roles and recipes. + # If you call it with arguments, they will become the new list of roles and recipes. def run_list(*args) args.length > 0 ? @run_list.reset!(args) : @run_list end - + def recipes(*args) Chef::Log.warn "Chef::Node#recipes method is deprecated. Please use Chef::Node#run_list" run_list(*args) @@ -328,22 +329,22 @@ class Chef def run_list?(item) @run_list.detect { |r| r == item } ? true : false end - + def consume_attributes(attrs) attrs ||= {} Chef::Log.debug("Adding JSON Attributes") - attrs.each do |key, value| - if ["recipes", "run_list"].include?(key) - run_list(value) - else - Chef::Log.debug("JSON Attribute: #{key} - #{value.inspect}") - store(key, value) + if new_run_list = attrs.delete("recipes") || attrs.delete("run_list") + if attrs.key?("recipes") || attrs.key?("run_list") + raise Chef::Exceptions::AmbiguousRunlistSpecification, "please set the node's run list using the 'run_list' attribute only." end + Chef::Log.info("Replacing the run_list with #{new_run_list.inspect} from JSON") + run_list(new_run_list) end + Chef::Mixin::DeepMerge.merge(@attribute, attrs) + self[:tags] = Array.new unless attribute?(:tags) - end - + # Transform the node to a Hash def to_hash index_hash = Hash.new @@ -357,8 +358,8 @@ class Chef index_hash["run_list"] = @run_list.run_list if @run_list.run_list.length > 0 index_hash end - - # Serialize this object as a hash + + # Serialize this object as a hash def to_json(*a) result = { "name" => @name, @@ -373,11 +374,12 @@ class Chef result["_rev"] = @couchdb_rev if @couchdb_rev result.to_json(*a) end - + # Create a Chef::Node from JSON def self.json_create(o) node = new node.name(o["name"]) + if o.has_key?("attributes") node.normal_attrs = o["attributes"] end @@ -396,7 +398,7 @@ class Chef node.index_id = node.couchdb_id node end - + # List all the Chef::Node objects in the CouchDB. If inflate is set to true, you will get # the full list of all Nodes, fully inflated. def self.cdb_list(inflate=false, couchdb=nil) @@ -416,10 +418,10 @@ class Chef Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("nodes") end end - + # Load a node by name from CouchDB def self.cdb_load(name, couchdb=nil) - (couchdb || Chef::CouchDB.new).load("node", name) + (couchdb || Chef::CouchDB.new).load("node", name) end def self.exists?(nodename, couchdb) @@ -429,12 +431,12 @@ class Chef nil end end - + # Load a node by name def self.load(name) Chef::REST.new(Chef::Config[:chef_server_url]).get_rest("nodes/#{name}") end - + # Remove this node from the CouchDB def cdb_destroy @couchdb.delete("node", @name, @couchdb_rev) @@ -444,7 +446,7 @@ class Chef def destroy chef_server_rest.delete_rest("nodes/#{@name}") end - + # Save this node to the CouchDB def cdb_save @couchdb_rev = @couchdb.store("node", @name, self)["rev"] @@ -460,18 +462,18 @@ class Chef end self end - + # Create the node via the REST API def create chef_server_rest.post_rest("nodes", self) self - end + end # Set up our CouchDB design document def self.create_design_document(couchdb=nil) (couchdb || Chef::CouchDB.new).create_design_document("nodes", DESIGN_DOCUMENT) end - + # As a string def to_s "node[#{@name}]" diff --git a/chef/lib/chef/openid_registration.rb b/chef/lib/chef/openid_registration.rb index fcc67dd08c..82bee0798e 100644 --- a/chef/lib/chef/openid_registration.rb +++ b/chef/lib/chef/openid_registration.rb @@ -21,7 +21,6 @@ require 'chef/mixin/params_validate' require 'chef/couchdb' require 'chef/index_queue' require 'digest/sha1' -require 'rubygems' require 'json' class Chef diff --git a/chef/lib/chef/platform.rb b/chef/lib/chef/platform.rb index 6d81f7a9e0..58ebc4d0fb 100644 --- a/chef/lib/chef/platform.rb +++ b/chef/lib/chef/platform.rb @@ -19,128 +19,162 @@ require 'chef/config' require 'chef/log' require 'chef/mixin/params_validate' -require 'chef/platform' -require 'chef/resource' -Dir[File.join(File.dirname(__FILE__), 'provider/**/*.rb')].sort.each { |lib| require lib } + +# Actually, this file depends on nearly every provider in chef, but actually +# requiring them causes circular requires resulting in uninitialized constant +# errors. +require 'chef/provider' +require 'chef/provider/log' +require 'chef/provider/user' +require 'chef/provider/group' +require 'chef/provider/mount' +require 'chef/provider/service' +require 'chef/provider/package' + class Chef class Platform - @platforms = { - :mac_os_x => { - :default => { - :package => Chef::Provider::Package::Macports, - :user => Chef::Provider::User::Dscl, - :group => Chef::Provider::Group::Dscl - } - }, - :freebsd => { - :default => { - :group => Chef::Provider::Group::Pw, - :package => Chef::Provider::Package::Freebsd, - :service => Chef::Provider::Service::Freebsd, - :user => Chef::Provider::User::Pw, - :cron => Chef::Provider::Cron - } - }, - :ubuntu => { - :default => { - :package => Chef::Provider::Package::Apt, - :service => Chef::Provider::Service::Debian, - :cron => Chef::Provider::Cron, - :mdadm => Chef::Provider::Mdadm - } - }, - :debian => { - :default => { - :package => Chef::Provider::Package::Apt, - :service => Chef::Provider::Service::Debian, - :cron => Chef::Provider::Cron, - :mdadm => Chef::Provider::Mdadm - } - }, - :centos => { - :default => { - :service => Chef::Provider::Service::Redhat, - :cron => Chef::Provider::Cron, - :package => Chef::Provider::Package::Yum, - :mdadm => Chef::Provider::Mdadm - } - }, - :fedora => { - :default => { - :service => Chef::Provider::Service::Redhat, - :cron => Chef::Provider::Cron, - :package => Chef::Provider::Package::Yum, - :mdadm => Chef::Provider::Mdadm - } - }, - :suse => { - :default => { - :service => Chef::Provider::Service::Redhat, - :cron => Chef::Provider::Cron, - :package => Chef::Provider::Package::Zypper - } - }, - :redhat => { - :default => { - :service => Chef::Provider::Service::Redhat, - :cron => Chef::Provider::Cron, - :package => Chef::Provider::Package::Yum, - :mdadm => Chef::Provider::Mdadm - } - }, - :gentoo => { - :default => { - :package => Chef::Provider::Package::Portage, - :service => Chef::Provider::Service::Gentoo, - :cron => Chef::Provider::Cron, - :mdadm => Chef::Provider::Mdadm - } - }, - :arch => { - :default => { - :package => Chef::Provider::Package::Pacman, - :service => Chef::Provider::Service::Arch, - :cron => Chef::Provider::Cron, - :mdadm => Chef::Provider::Mdadm - } - }, - :solaris => {}, - :default => { - :file => Chef::Provider::File, - :directory => Chef::Provider::Directory, - :link => Chef::Provider::Link, - :template => Chef::Provider::Template, - :remote_file => Chef::Provider::RemoteFile, - :remote_directory => Chef::Provider::RemoteDirectory, - :execute => Chef::Provider::Execute, - :mount => Chef::Provider::Mount::Mount, - :script => Chef::Provider::Script, - :service => Chef::Provider::Service::Init, - :perl => Chef::Provider::Script, - :python => Chef::Provider::Script, - :ruby => Chef::Provider::Script, - :bash => Chef::Provider::Script, - :csh => Chef::Provider::Script, - :user => Chef::Provider::User::Useradd, - :group => Chef::Provider::Group::Gpasswd, - :http_request => Chef::Provider::HttpRequest, - :route => Chef::Provider::Route, - :ifconfig => Chef::Provider::Ifconfig, - :ruby_block => Chef::Provider::RubyBlock, - :erl_call => Chef::Provider::ErlCall, - :log => Chef::Provider::Log::ChefLog - } - } - class << self - attr_accessor :platforms + attr_writer :platforms + + def platforms + @platforms ||= { + :mac_os_x => { + :default => { + :package => Chef::Provider::Package::Macports, + :user => Chef::Provider::User::Dscl, + :group => Chef::Provider::Group::Dscl + } + }, + :freebsd => { + :default => { + :group => Chef::Provider::Group::Pw, + :package => Chef::Provider::Package::Freebsd, + :service => Chef::Provider::Service::Freebsd, + :user => Chef::Provider::User::Pw, + :cron => Chef::Provider::Cron + } + }, + :ubuntu => { + :default => { + :package => Chef::Provider::Package::Apt, + :service => Chef::Provider::Service::Debian, + :cron => Chef::Provider::Cron, + :mdadm => Chef::Provider::Mdadm + } + }, + :debian => { + :default => { + :package => Chef::Provider::Package::Apt, + :service => Chef::Provider::Service::Debian, + :cron => Chef::Provider::Cron, + :mdadm => Chef::Provider::Mdadm + } + }, + :centos => { + :default => { + :service => Chef::Provider::Service::Redhat, + :cron => Chef::Provider::Cron, + :package => Chef::Provider::Package::Yum, + :mdadm => Chef::Provider::Mdadm + } + }, + :scientific => { + :default => { + :service => Chef::Provider::Service::Redhat, + :cron => Chef::Provider::Cron, + :package => Chef::Provider::Package::Yum, + :mdadm => Chef::Provider::Mdadm + } + }, + :fedora => { + :default => { + :service => Chef::Provider::Service::Redhat, + :cron => Chef::Provider::Cron, + :package => Chef::Provider::Package::Yum, + :mdadm => Chef::Provider::Mdadm + } + }, + :suse => { + :default => { + :service => Chef::Provider::Service::Redhat, + :cron => Chef::Provider::Cron, + :package => Chef::Provider::Package::Zypper + } + }, + :redhat => { + :default => { + :service => Chef::Provider::Service::Redhat, + :cron => Chef::Provider::Cron, + :package => Chef::Provider::Package::Yum, + :mdadm => Chef::Provider::Mdadm + } + }, + :gentoo => { + :default => { + :package => Chef::Provider::Package::Portage, + :service => Chef::Provider::Service::Gentoo, + :cron => Chef::Provider::Cron, + :mdadm => Chef::Provider::Mdadm + } + }, + :arch => { + :default => { + :package => Chef::Provider::Package::Pacman, + :service => Chef::Provider::Service::Arch, + :cron => Chef::Provider::Cron, + :mdadm => Chef::Provider::Mdadm + } + }, + :mswin => { + :default => { + :service => Chef::Provider::Service::Windows + } + }, + :mingw32 => { + :default => { + :service => Chef::Provider::Service::Windows + } + }, + :windows => { + :default => { + :service => Chef::Provider::Service::Windows + } + }, + :solaris => {}, + :default => { + :file => Chef::Provider::File, + :directory => Chef::Provider::Directory, + :link => Chef::Provider::Link, + :template => Chef::Provider::Template, + :remote_file => Chef::Provider::RemoteFile, + :remote_directory => Chef::Provider::RemoteDirectory, + :execute => Chef::Provider::Execute, + :mount => Chef::Provider::Mount::Mount, + :script => Chef::Provider::Script, + :service => Chef::Provider::Service::Init, + :perl => Chef::Provider::Script, + :python => Chef::Provider::Script, + :ruby => Chef::Provider::Script, + :bash => Chef::Provider::Script, + :csh => Chef::Provider::Script, + :user => Chef::Provider::User::Useradd, + :group => Chef::Provider::Group::Gpasswd, + :http_request => Chef::Provider::HttpRequest, + :route => Chef::Provider::Route, + :ifconfig => Chef::Provider::Ifconfig, + :ruby_block => Chef::Provider::RubyBlock, + :erl_call => Chef::Provider::ErlCall, + :log => Chef::Provider::Log::ChefLog + } + } + end include Chef::Mixin::ParamsValidate def find(name, version) - provider_map = @platforms[:default].clone + provider_map = platforms[:default].clone name_sym = name if name.kind_of?(String) @@ -149,15 +183,15 @@ class Chef name_sym = name.to_sym end - if @platforms.has_key?(name_sym) - if @platforms[name_sym].has_key?(version) + if platforms.has_key?(name_sym) + if platforms[name_sym].has_key?(version) Chef::Log.debug("Platform #{name.to_s} version #{version} found") - if @platforms[name_sym].has_key?(:default) - provider_map.merge!(@platforms[name_sym][:default]) + if platforms[name_sym].has_key?(:default) + provider_map.merge!(platforms[name_sym][:default]) end - provider_map.merge!(@platforms[name_sym][version]) - elsif @platforms[name_sym].has_key?(:default) - provider_map.merge!(@platforms[name_sym][:default]) + provider_map.merge!(platforms[name_sym][version]) + elsif platforms[name_sym].has_key?(:default) + provider_map.merge!(platforms[name_sym][:default]) end else Chef::Log.debug("Platform #{name} not found, using all defaults. (Unsupported platform?)") @@ -221,30 +255,30 @@ class Chef ) if args.has_key?(:platform) if args.has_key?(:version) - if @platforms.has_key?(args[:platform]) - if @platforms[args[:platform]].has_key?(args[:version]) - @platforms[args[:platform]][args[:version]][args[:resource].to_sym] = args[:provider] + if platforms.has_key?(args[:platform]) + if platforms[args[:platform]].has_key?(args[:version]) + platforms[args[:platform]][args[:version]][args[:resource].to_sym] = args[:provider] else - @platforms[args[:platform]][args[:version]] = { + platforms[args[:platform]][args[:version]] = { args[:resource].to_sym => args[:provider] } end else - @platforms[args[:platform]] = { + platforms[args[:platform]] = { args[:version] => { args[:resource].to_sym => args[:provider] } } end else - if @platforms.has_key?(args[:platform]) - if @platforms[args[:platform]].has_key?(:default) - @platforms[args[:platform]][:default][args[:resource].to_sym] = args[:provider] + if platforms.has_key?(args[:platform]) + if platforms[args[:platform]].has_key?(:default) + platforms[args[:platform]][:default][args[:resource].to_sym] = args[:provider] else - @platforms[args[:platform]] = { :default => { args[:resource].to_sym => args[:provider] } } + platforms[args[:platform]] = { :default => { args[:resource].to_sym => args[:provider] } } end else - @platforms[args[:platform]] = { + platforms[args[:platform]] = { :default => { args[:resource].to_sym => args[:provider] } @@ -252,10 +286,10 @@ class Chef end end else - if @platforms.has_key?(:default) - @platforms[:default][args[:resource].to_sym] = args[:provider] + if platforms.has_key?(:default) + platforms[:default][args[:resource].to_sym] = args[:provider] else - @platforms[:default] = { + platforms[:default] = { args[:resource].to_sym => args[:provider] } end @@ -264,8 +298,8 @@ class Chef def find_provider(platform, version, resource_type) pmap = Chef::Platform.find(platform, version) - provider_klass = explicit_provider(platform, version, resource_type) || - platform_provider(platform, version, resource_type) || + provider_klass = explicit_provider(platform, version, resource_type) || + platform_provider(platform, version, resource_type) || resource_matching_provider(platform, version, resource_type) raise ArgumentError, "Cannot find a provider for #{resource_type} on #{platform} version #{version}" if provider_klass.nil? @@ -288,8 +322,8 @@ class Chef def resource_matching_provider(platform, version, resource_type) if resource_type.kind_of?(Chef::Resource) begin - Chef::Provider.const_get(resource_type.class.to_s.split('::').last) - rescue NameError + Chef::Provider.const_get(resource_type.class.to_s.split('::').last) + rescue NameError nil end else diff --git a/chef/lib/chef/provider.rb b/chef/lib/chef/provider.rb index 2191aabc94..bd7c50a065 100644 --- a/chef/lib/chef/provider.rb +++ b/chef/lib/chef/provider.rb @@ -49,10 +49,10 @@ class Chef protected - def recipe_eval(*args, &block) + def recipe_eval(&block) provider_collection, @collection = @collection, Chef::ResourceCollection.new - instance_eval(*args, &block) + instance_eval(&block) Chef::Runner.new(@node, @collection).converge @collection = provider_collection diff --git a/chef/lib/chef/provider/cron.rb b/chef/lib/chef/provider/cron.rb index 25467ffbf6..df3f9e66fe 100644 --- a/chef/lib/chef/provider/cron.rb +++ b/chef/lib/chef/provider/cron.rb @@ -6,9 +6,9 @@ # 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. @@ -35,19 +35,19 @@ class Chef attr_accessor :cron_exists, :cron_empty def load_current_resource - crontab = String.new + crontab_lines = [] @current_resource = Chef::Resource::Cron.new(@new_resource.name) @current_resource.user(@new_resource.user) status = popen4("crontab -l -u #{@new_resource.user}") do |pid, stdin, stdout, stderr| - stdout.each { |line| crontab << line } + stdout.each_line { |line| crontab_lines << line } end if status.exitstatus > 1 raise Chef::Exceptions::Cron, "Error determining state of #{@new_resource.name}, exit: #{status.exitstatus}" elsif status.exitstatus == 0 cron_found = false - crontab.each do |line| - case line - when /^# Chef Name: #{@new_resource.name}/ + crontab_lines.each do |line| + case line.chomp + when "# Chef Name: #{@new_resource.name}" Chef::Log.debug("Found cron '#{@new_resource.name}'") cron_found = true @cron_exists = true @@ -66,11 +66,11 @@ class Chef next when CRON_PATTERN if cron_found - @current_resource.minute($1) - @current_resource.hour($2) + @current_resource.minute($1) + @current_resource.hour($2) @current_resource.day($3) - @current_resource.month($4) - @current_resource.weekday($5) + @current_resource.month($4) + @current_resource.weekday($5) @current_resource.command($6) cron_found=false end @@ -84,7 +84,7 @@ class Chef Chef::Log.debug("Cron empty for '#{@new_resource.user}'") @cron_empty = true end - + @current_resource end @@ -112,8 +112,8 @@ class Chef end status = popen4("crontab -l -u #{@new_resource.user}") do |pid, stdin, stdout, stderr| stdout.each_line do |line| - case line - when /^# Chef Name: #{@new_resource.name}\n/ + case line.chomp + when "# Chef Name: #{@new_resource.name}" cron_found = true next when CRON_PATTERN @@ -125,12 +125,12 @@ class Chef else next if cron_found end - crontab << line + crontab << line end end status = popen4("crontab -u #{@new_resource.user} -", :waitlast => true) do |pid, stdin, stdout, stderr| - crontab.each { |line| stdin.puts "#{line}" } + crontab.each_line { |line| stdin.puts "#{line}" } end Chef::Log.info("Updated cron '#{@new_resource.name}'") @new_resource.updated = true @@ -140,11 +140,11 @@ class Chef stdout.each { |line| crontab << line } end end - + crontab << newcron status = popen4("crontab -u #{@new_resource.user} -", :waitlast => true) do |pid, stdin, stdout, stderr| - crontab.each { |line| stdin.puts "#{line}" } + crontab.each_line { |line| stdin.puts "#{line}" } end Chef::Log.info("Added cron '#{@new_resource.name}'") @new_resource.updated = true @@ -157,8 +157,8 @@ class Chef cron_found = false status = popen4("crontab -l -u #{@new_resource.user}") do |pid, stdin, stdout, stderr| stdout.each_line do |line| - case line - when /^# Chef Name: #{@new_resource.name}\n/ + case line.chomp + when "# Chef Name: #{@new_resource.name}" cron_found = true next when CRON_PATTERN @@ -169,12 +169,12 @@ class Chef else next if cron_found end - crontab << line + crontab << line end end status = popen4("crontab -u #{@new_resource.user} -", :waitlast => true) do |pid, stdin, stdout, stderr| - crontab.each { |line| stdin.puts "#{line}" } + crontab.each_line { |line| stdin.puts "#{line}" } end Chef::Log.debug("Deleted cron '#{@new_resource.name}'") @new_resource.updated = true diff --git a/chef/lib/chef/provider/deploy/revision.rb b/chef/lib/chef/provider/deploy/revision.rb index aea4901f6b..00a30515ba 100644 --- a/chef/lib/chef/provider/deploy/revision.rb +++ b/chef/lib/chef/provider/deploy/revision.rb @@ -50,11 +50,15 @@ class Chef cache end + def sorted_releases_from_filesystem + Dir.glob(new_resource.deploy_to + "/releases/*").sort_by { |d| ::File.ctime(d) } + end + def load_cache begin JSON.parse(Chef::FileCache.load("revision-deploys/#{new_resource.name}")) rescue Chef::Exceptions::FileNotFound - save_cache([]) + sorted_releases_from_filesystem end end diff --git a/chef/lib/chef/provider/erl_call.rb b/chef/lib/chef/provider/erl_call.rb index 39b2dcfab6..7d29e24081 100644 --- a/chef/lib/chef/provider/erl_call.rb +++ b/chef/lib/chef/provider/erl_call.rb @@ -59,11 +59,11 @@ class Chef Chef::Log.debug("Running erl_call[#{@new_resource.name}]") Chef::Log.debug("erl_call[#{@new_resource.name}] command: #{command}") Chef::Log.debug("erl_call[#{@new_resource.name}] code: #{@new_resource.code}") - @new_resource.code.each { |line| stdin.puts "#{line.chomp!}" } + @new_resource.code.each_line { |line| stdin.puts "#{line.chomp!}" } stdin.close Chef::Log.info("Ran erl_call[#{@new_resource.name}] successfully") Chef::Log.debug("erl_call[#{@new_resource.name}] output: ") - stdout.each { |line| Chef::Log.debug("#{line}")} + stdout.each_line { |line| Chef::Log.debug("#{line}")} end end diff --git a/chef/lib/chef/provider/file.rb b/chef/lib/chef/provider/file.rb index 0bb2c4529b..969c7c9703 100644 --- a/chef/lib/chef/provider/file.rb +++ b/chef/lib/chef/provider/file.rb @@ -46,6 +46,7 @@ class Chef def load_current_resource @current_resource = Chef::Resource::File.new(@new_resource.name) + @new_resource.path.gsub!(/\\/, "/") # for Windows @current_resource.path(@new_resource.path) if ::File.exist?(@current_resource.path) && ::File.readable?(@current_resource.path) cstats = ::File.stat(@current_resource.path) diff --git a/chef/lib/chef/provider/group/dscl.rb b/chef/lib/chef/provider/group/dscl.rb index 905a1deaaf..4462aa28a6 100644 --- a/chef/lib/chef/provider/group/dscl.rb +++ b/chef/lib/chef/provider/group/dscl.rb @@ -75,11 +75,13 @@ class Chef def set_members unless @new_resource.append Chef::Log.debug("#{@new_resource}: removing group members #{@current_resource.members.join(' ')}") unless @current_resource.members.empty? - safe_dscl("create /Groups/#{@new_resource.group_name} GroupMembers") # clear guid list - safe_dscl("create /Groups/#{@new_resource.group_name} GroupMembership") # clear user list + safe_dscl("create /Groups/#{@new_resource.group_name} GroupMembers ''") # clear guid list + safe_dscl("create /Groups/#{@new_resource.group_name} GroupMembership ''") # clear user list + end + unless @new_resource.members.empty? + Chef::Log.debug("#{@new_resource}: setting group members #{@new_resource.members.join(', ')}") + safe_dscl("append /Groups/#{@new_resource.group_name} GroupMembership #{@new_resource.members.join(' ')}") end - Chef::Log.debug("#{@new_resource}: setting group members #{@new_resource.members.join(', ')}") unless @new_resource.members.empty? - safe_dscl("append /Groups/#{@new_resource.group_name} GroupMembership #{@new_resource.members.join(' ')}") end def load_current_resource diff --git a/chef/lib/chef/provider/mdadm.rb b/chef/lib/chef/provider/mdadm.rb index 6a22c289b4..6df655a186 100644 --- a/chef/lib/chef/provider/mdadm.rb +++ b/chef/lib/chef/provider/mdadm.rb @@ -26,10 +26,6 @@ class Chef include Chef::Mixin::Command - def initialize(node, new_resource) - super(node, new_resource) - end - def load_current_resource @current_resource = Chef::Resource::Mdadm.new(@new_resource.name) @current_resource.raid_device(@new_resource.raid_device) diff --git a/chef/lib/chef/provider/mount/mount.rb b/chef/lib/chef/provider/mount/mount.rb index 1669ddebb5..18bc8441bc 100644 --- a/chef/lib/chef/provider/mount/mount.rb +++ b/chef/lib/chef/provider/mount/mount.rb @@ -64,12 +64,16 @@ class Chef # Check to see if there is a entry in /etc/fstab. Last entry for a volume wins. enabled = false - ::File.read("/etc/fstab").each do |line| + ::File.foreach("/etc/fstab") do |line| case line when /^[#\s]/ next - when /^#{device_fstab_regex}\s+#{@new_resource.mount_point}/ + when /^#{device_fstab_regex}\s+#{@new_resource.mount_point}\s+(\S+)\s+(\S+)\s+(\S+)\s+(\S+)/ enabled = true + @current_resource.fstype($1) + @current_resource.options($2) + @current_resource.dump($3.to_i) + @current_resource.pass($4.to_i) Chef::Log.debug("Found mount #{device_fstab} to #{@new_resource.mount_point} in /etc/fstab") when /^[\/\w]+\s+#{@new_resource.mount_point}/ enabled = false @@ -119,13 +123,21 @@ class Chef end def enable_fs - unless @current_resource.enabled - ::File.open("/etc/fstab", "a") do |fstab| - fstab.puts("#{device_fstab} #{@new_resource.mount_point} #{@new_resource.fstype} #{@new_resource.options.nil? ? "defaults" : @new_resource.options.join(",")} #{@new_resource.dump} #{@new_resource.pass}") - Chef::Log.info("Enabled #{@new_resource.mount_point}") + if @current_resource.enabled + if @current_resource.fstype == @new_resource.fstype and + @current_resource.options == @new_resource.options and + @current_resource.dump == @new_resource.dump and + @current_resource.pass == @new_resource.pass + Chef::Log.debug("#{@new_resource.mount_point} is already enabled.") + return end - else - Chef::Log.debug("#{@new_resource.mount_point} is already enabled.") + # The current options don't match what we have, so + # disable, then enable. + disable_fs + end + ::File.open("/etc/fstab", "a") do |fstab| + fstab.puts("#{device_fstab} #{@new_resource.mount_point} #{@new_resource.fstype} #{@new_resource.options.nil? ? "defaults" : @new_resource.options.join(",")} #{@new_resource.dump} #{@new_resource.pass}") + Chef::Log.info("Enabled #{@new_resource.mount_point}") end end diff --git a/chef/lib/chef/provider/package.rb b/chef/lib/chef/provider/package.rb index bf6dd4cf47..6f0ecbbd9d 100644 --- a/chef/lib/chef/provider/package.rb +++ b/chef/lib/chef/provider/package.rb @@ -120,7 +120,7 @@ class Chef raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support :purge" end - def preseed_package(name, version, preseed) + def preseed_package(name, version) raise Chef::Exceptions::UnsupportedAction, "#{self.to_s} does not support pre-seeding package install/upgrade instructions - don't ask it to!" end diff --git a/chef/lib/chef/provider/package/freebsd.rb b/chef/lib/chef/provider/package/freebsd.rb index bfe67c8b73..18dd38107c 100644 --- a/chef/lib/chef/provider/package/freebsd.rb +++ b/chef/lib/chef/provider/package/freebsd.rb @@ -7,9 +7,9 @@ # 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. @@ -24,8 +24,13 @@ require 'chef/resource/package' class Chef class Provider class Package - class Freebsd < Chef::Provider::Package - + class Freebsd < Chef::Provider::Package + + def initialize(*args) + super + @current_resource = Chef::Resource::Package.new(@new_resource.name) + end + def current_installed_version command = "pkg_info -E \"#{package_name}*\"" status = popen4(command) do |pid, stdin, stdout, stderr| @@ -41,7 +46,7 @@ class Chef end nil end - + def port_path case @new_resource.package_name # When the package name starts with a '/' treat it as the full path to the ports directory @@ -61,10 +66,10 @@ class Chef end end end - raise Chef::Exception::Package, "Could not find port with the name #{@new_resource.package_name}" - end + raise Chef::Exceptions::Package, "Could not find port with the name #{@new_resource.package_name}" + end end - + def ports_makefile_variable_value(variable) command = "cd #{port_path}; make -V #{variable}" status = popen4(command) do |pid, stdin, stdout, stderr| @@ -75,34 +80,33 @@ class Chef end nil end - + def ports_candidate_version ports_makefile_variable_value("PORTVERSION") end - + def load_current_resource - @current_resource = Chef::Resource::Package.new(@new_resource.name) @current_resource.package_name(@new_resource.package_name) - + @current_resource.version(current_installed_version) Chef::Log.debug("Current version is #{@current_resource.version}") if @current_resource.version - + @candidate_version = ports_candidate_version Chef::Log.debug("Ports candidate version is #{@candidate_version}") if @candidate_version - + @current_resource end - + def latest_link_name ports_makefile_variable_value("LATEST_LINK") end - + # The name of the package (without the version number) as understood by pkg_add and pkg_info def package_name if ports_makefile_variable_value("PKGNAME") =~ /^(.+)-[^-]+$/ $1 else - raise Chef::Exception::Package, "Unexpected form for PKGNAME variable in #{port_path}/Makefile" + raise Chef::Exceptions::Package, "Unexpected form for PKGNAME variable in #{port_path}/Makefile" end end @@ -134,7 +138,7 @@ class Chef end end end - + def remove_package(name, version) # a version is mandatory if version diff --git a/chef/lib/chef/provider/package/rubygems.rb b/chef/lib/chef/provider/package/rubygems.rb index c6392d91a3..d9c80f2e33 100644 --- a/chef/lib/chef/provider/package/rubygems.rb +++ b/chef/lib/chef/provider/package/rubygems.rb @@ -27,9 +27,8 @@ class Chef def gem_list_parse(line) installed_versions = Array.new - if line.match("^#{@new_resource.package_name} \\((.+?)\\)$") - installed_versions = $1.split(/, /) - installed_versions + if md = line.match(/^#{@new_resource.package_name} \((.+?)(?: [^\)\.]+)?\)$/) + md.captures.first.split(/, /) else nil end @@ -47,9 +46,8 @@ class Chef # First, we need to look up whether we have the local gem installed or not status = popen4("#{gem_binary_path} list --local #{@new_resource.package_name}") do |pid, stdin, stdout, stderr| - stdout.each do |line| - installed_versions = gem_list_parse(line) - next unless installed_versions + stdout.each_line do |line| + next unless installed_versions = gem_list_parse(line) # If the version we are asking for is installed, make that our current # version. Otherwise, go ahead and use the highest one, which # happens to come first in the array. @@ -75,9 +73,8 @@ class Chef return @candidate_version if @candidate_version status = popen4("#{gem_binary_path} list --remote #{@new_resource.package_name}#{' --source=' + @new_resource.source if @new_resource.source}") do |pid, stdin, stdout, stderr| - stdout.each do |line| - installed_versions = gem_list_parse(line) - next unless installed_versions + stdout.each_line do |line| + next unless installed_versions = gem_list_parse(line) Chef::Log.debug("candidate_version: remote rubygem(s) available: #{installed_versions.inspect}") unless installed_versions.empty? @@ -97,7 +94,7 @@ class Chef def install_package(name, version) src = nil if @new_resource.source - src = " --source=#{@new_resource.source} --source=http://gems.rubyforge.org" + src = " --source=#{@new_resource.source} --source=http://rubygems.org" end run_command_with_systems_locale( :command => "#{gem_binary_path} install #{name} -q --no-rdoc --no-ri -v \"#{version}\"#{src}#{opts}" diff --git a/chef/lib/chef/provider/remote_directory.rb b/chef/lib/chef/provider/remote_directory.rb index b5b65367fa..2816e54d51 100644 --- a/chef/lib/chef/provider/remote_directory.rb +++ b/chef/lib/chef/provider/remote_directory.rb @@ -26,6 +26,7 @@ require 'chef/platform' require 'uri' require 'tempfile' require 'net/https' +require 'set' class Chef class Provider @@ -47,8 +48,22 @@ class Chef Chef::Log.debug("Doing a remote recursive directory transfer for #{@new_resource}") end + existing_files = Set.new Dir[::File.join(@new_resource.path, '**', '*')] + files_to_transfer.each do |remote_file_source| fetch_remote_file(remote_file_source) + existing_files.delete(::File.join(@new_resource.path, remote_file_source)) + end + if @new_resource.purge + existing_files.sort { |a,b| b <=> a }.each do |f| + if ::File.directory?(f) + Chef::Log.debug("Removing directory #{f}") + Dir::rmdir(f) + else + Chef::Log.debug("Deleting file #{f}") + ::File.delete(f) + end + end end end diff --git a/chef/lib/chef/provider/remote_file.rb b/chef/lib/chef/provider/remote_file.rb index 247549ed95..c8904c2941 100644 --- a/chef/lib/chef/provider/remote_file.rb +++ b/chef/lib/chef/provider/remote_file.rb @@ -45,42 +45,20 @@ class Chef def do_remote_file(source, path) retval = true - if(@new_resource.checksum && @current_resource.checksum && @current_resource.checksum =~ /^#{@new_resource.checksum}/) - Chef::Log.debug("File #{@new_resource} checksum matches, not updating") + if current_resource_matches_target_checksum? + Chef::Log.debug("File #{@new_resource} checksum matches target checksum (#{@new_resource.checksum}), not updating") else begin - # The remote filehandle - raw_file = get_from_uri(source) || - get_from_server(source, @current_resource.checksum) || - get_from_local_cookbook(source) - - # If the file exists - Chef::Log.debug "#{@new_resource}: Checking for file existence of #{@new_resource.path}" - if ::File.exists?(@new_resource.path) - # And it matches the checksum of the raw file - @new_resource.checksum(self.checksum(raw_file.path)) - Chef::Log.debug "#{@new_resource}: File exists at #{@new_resource.path}" - Chef::Log.debug "#{@new_resource}: Target checksum: #{@current_resource.checksum}" - Chef::Log.debug "#{@new_resource}: Source checksum: #{@new_resource.checksum}" - if @new_resource.checksum != @current_resource.checksum - # Updating target file, let's perform a backup! - Chef::Log.debug "#{@new_resource}: checksum changed from #{@current_resource.checksum} to #{@new_resource.checksum}" - Chef::Log.info "#{@new_resource}: Updating #{@new_resource.path}" - backup @new_resource.path + source_file(source, @current_resource.checksum) do |raw_file| + if matches_current_checksum?(raw_file) + Chef::Log.debug "#{@new_resource}: Target and Source checksums are the same, taking no action" + else + backup_new_resource + Chef::Log.debug "copying remote file from origin #{raw_file.path} to destination #{@new_resource.path}" FileUtils.cp raw_file.path, @new_resource.path @new_resource.updated = true - else - Chef::Log.debug "#{@new_resource}: Target and Source checksums are the same, taking no action" end - else - # We're creating a new file - Chef::Log.info "#{@new_resource}: Creating #{@new_resource.path}" - FileUtils.cp raw_file.path, @new_resource.path - @new_resource.updated = true end - - # We're done with the file, so make sure to close it if it was open. - raw_file.close unless raw_file.closed? rescue Net::HTTPRetriableError => e if e.response.kind_of?(Net::HTTPNotModified) Chef::Log.debug("File #{path} is unchanged") @@ -89,38 +67,78 @@ class Chef raise e end end + + Chef::Log.debug "#{@new_resource} completed" + retval end - + enforce_ownership_and_permissions + + retval + end + + def enforce_ownership_and_permissions set_owner if @new_resource.owner set_group if @new_resource.group set_mode if @new_resource.mode - retval end - def get_from_uri(source) - begin - uri = URI.parse(source) - if uri.absolute - r = Chef::REST.new(source, nil, nil) - Chef::Log.debug("Downloading from absolute URI: #{source}") - r.get_rest(source, true).open - end - rescue URI::InvalidURIError - nil + def current_resource_matches_target_checksum? + @new_resource.checksum && @current_resource.checksum && @current_resource.checksum =~ /^#{@new_resource.checksum}/ + end + + def matches_current_checksum?(candidate_file) + Chef::Log.debug "#{@new_resource}: Checking for file existence of #{@new_resource.path}" + if ::File.exists?(@new_resource.path) + Chef::Log.debug "#{@new_resource}: File exists at #{@new_resource.path}" + @new_resource.checksum(checksum(candidate_file.path)) + Chef::Log.debug "#{@new_resource}: Target checksum: #{@current_resource.checksum}" + Chef::Log.debug "#{@new_resource}: Source checksum: #{@new_resource.checksum}" + @new_resource.checksum == @current_resource.checksum + else + Chef::Log.info "#{@new_resource}: Creating #{@new_resource.path}" + false + end + end + + def backup_new_resource + if ::File.exists?(@new_resource.path) + Chef::Log.debug "#{@new_resource}: checksum changed from #{@current_resource.checksum} to #{@new_resource.checksum}" + Chef::Log.info "#{@new_resource}: Updating #{@new_resource.path}" + backup @new_resource.path end end - def get_from_server(source, current_checksum) - unless Chef::Config[:solo] - r = Chef::REST.new(Chef::Config[:remotefile_url]) - url = generate_url(source, "files", :checksum => current_checksum) - Chef::Log.debug("Downloading from server: #{url}") - r.get_rest(url, true).open + def source_file(source, current_checksum, &block) + if absolute_uri?(source) + fetch_from_uri(source, &block) + elsif !Chef::Config[:solo] + fetch_from_chef_server(source, current_checksum, &block) + else + fetch_from_local_cookbook(source, &block) end end - def get_from_local_cookbook(source) + private + + def absolute_uri?(source) + URI.parse(source).absolute? + rescue URI::InvalidURIError + false + end + + def fetch_from_uri(source) + Chef::Log.debug("Downloading from absolute URI: #{source}") + Chef::REST.new(source, nil, nil).fetch(source) { |tmp_file| yield tmp_file } + end + + def fetch_from_chef_server(source, current_checksum) + url = generate_url(source, "files", :checksum => current_checksum) + Chef::Log.debug("Downloading #{@new_resource} from server: #{url}") + Chef::REST.new(Chef::Config[:remotefile_url]).fetch(url) { |tmp_file| yield tmp_file } + end + + def fetch_from_local_cookbook(source) if Chef::Config[:solo] cookbook_name = @new_resource.cookbook || @new_resource.cookbook_name filename = find_preferred_file( @@ -132,7 +150,12 @@ class Chef @node[:platform_version] ) Chef::Log.debug("Using local file for remote_file:#{filename}") - ::File.open(filename) + begin + file = ::File.open(filename) + yield file + ensure + file.close + end end end diff --git a/chef/lib/chef/provider/script.rb b/chef/lib/chef/provider/script.rb index 6fd4a367a0..8a7bbb69f5 100644 --- a/chef/lib/chef/provider/script.rb +++ b/chef/lib/chef/provider/script.rb @@ -6,9 +6,9 @@ # 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. @@ -22,21 +22,23 @@ require 'chef/provider/execute' class Chef class Provider class Script < Chef::Provider::Execute - - def action_run + + def action_run tf = Tempfile.new("chef-script") tf.puts(@new_resource.code) tf.close - + fr = Chef::Resource::File.new(tf.path, nil, @node) fr.owner(@new_resource.user) fr.group(@new_resource.group) fr.run_action(:create) - - @new_resource.command("#{@new_resource.interpreter} #{tf.path}") + + @new_resource.command("#{@new_resource.interpreter} #{tf.path}") super + ensure + tf && tf.close! end - + end end end
\ No newline at end of file diff --git a/chef/lib/chef/provider/service/windows.rb b/chef/lib/chef/provider/service/windows.rb new file mode 100644 index 0000000000..69920c08eb --- /dev/null +++ b/chef/lib/chef/provider/service/windows.rb @@ -0,0 +1,129 @@ +# +# 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 'chef/provider/service/init' + +class Chef::Provider::Service::Windows < Chef::Provider::Service::Init + + def initialize(node, new_resource, collection=nil, definitions=nil, cookbook_loader=nil) + super(node, new_resource, collection, definitions, cookbook_loader) + @init_command = "sc" + end + + def load_current_resource + @current_resource = Chef::Resource::Service.new(@new_resource.name) + @current_resource.service_name(@new_resource.service_name) + status = IO.popen("#{@init_command} query #{@new_resource.service_name}").entries + raise Chef::Exceptions::Exec, "Service #{@new_resource.service_name} does not exist.\n#{status.join}\n" if status[0].include?("FAILED 1060") + + begin + started = status[3].include?("4") + @current_resource.running started + + start_type = IO.popen("#{@init_command} qc #{@new_resource.service_name}").entries[4] + @current_resource.enabled(start_type.include?('2') || start_type.include?('3') ? true : false) + + Chef::Log.debug "#{@new_resource}: running: #{@current_resource.running}" + rescue StandardError + raise Chef::Exceptions::Exec + rescue Chef::Exceptions::Exec + Chef::Log.debug "Failed to determine the current status of the service, assuming it is not running" + @current_resource.running false + nil + end + @current_resource + end + + def start_service + begin + result = if @new_resource.start_command + Chef::Log.debug "starting service using the given start_command" + IO.popen(@new_resource.start_command).readlines + else + IO.popen("#{@init_command} start #{@new_resource.service_name}").readlines + end + Chef::Log.debug result.join + result[3].include?('4') || result.include?('2') ? true : false + rescue + Chef::Log.debug "Failed to start service #{@new_resource.service_name}" + false + end + end + + def stop_service + begin + Chef::Log.debug "stopping service using the given stop_command" + result = if @new_resource.stop_command + IO.popen(@new_resource.stop_command).readlines + else + IO.popen("#{@init_command} stop #{@new_resource.service_name}").readlines + end + Chef::Log.debug result.join + result[3].include?('1') + rescue + Chef::Log.debug "Failed to stop service #{@new_resource.service_name}" + false + end + end + + def restart_service + begin + if @new_resource.restart_command + Chef::Log.debug "restarting service using the given restart_command" + result = IO.popen(@new_resource.restart_command).readlines + Chef::Log.debug result.join + else + Chef::Log.debug IO.popen("#{@init_command} stop #{@new_resource.service_name}").readlines.join + sleep 1 + result = IO.popen("#{@init_command} start #{@new_resource.service_name}").readlines + Chef::Log.debug result.join + end + result[3].include?('4') || result.include?('2') + rescue + Chef::Log.debug "Failed to restart service #{@new_resource.service_name}" + false + end + end + + def enable_service() + begin + Chef::Log.debug result = IO.popen("#{@init_command} config #{@new_resource.service_name} start= #{determine_startup_type}").readlines.join + result.include?('SUCCESS') + rescue + Chef::Log.debug "Failed to enable service #{@new_resource.service_name}" + false + end + end + + def disable_service() + begin + Chef::Log.debug result = IO.popen("#{@init_command} config #{@new_resource.service_name} start= disabled").readlines.join + result.include?('SUCCESS') + rescue + Chef::Log.debug "Failed to disable service #{@new_resource.service_name}" + false + end + end + + private + + def determine_startup_type + {:automatic => 'auto', :mannual => 'demand'}[@new_resource.startup_type] + end + +end
\ No newline at end of file diff --git a/chef/lib/chef/provider/subversion.rb b/chef/lib/chef/provider/subversion.rb index 826dd9b35d..c58805e4d3 100644 --- a/chef/lib/chef/provider/subversion.rb +++ b/chef/lib/chef/provider/subversion.rb @@ -86,7 +86,7 @@ class Chef if @new_resource.revision =~ /^\d+$/ @new_resource.revision else - command = scm(:info, @new_resource.repository, authentication, "-r#{@new_resource.revision}") + command = scm(:info, @new_resource.repository, @new_resource.svn_info_args, authentication, "-r#{@new_resource.revision}") status, svn_info, error_message = output_of_command(command, run_options) handle_command_failures(status, "STDOUT: #{svn_info}\nSTDERR: #{error_message}") extract_revision_info(svn_info) diff --git a/chef/lib/chef/provider/template.rb b/chef/lib/chef/provider/template.rb index 89a51718a8..47a5ed6ca7 100644 --- a/chef/lib/chef/provider/template.rb +++ b/chef/lib/chef/provider/template.rb @@ -6,9 +6,9 @@ # 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. @@ -27,63 +27,64 @@ require 'tempfile' class Chef class Provider - + class Template < Chef::Provider::File - + include Chef::Mixin::Checksum include Chef::Mixin::Template include Chef::Mixin::FindPreferredFile - + def action_create raw_template_file = nil - + Chef::Log.debug("looking for template #{@new_resource.source} in cookbook #{cookbook_name.inspect}") - + cache_file_name = "cookbooks/#{cookbook_name}/templates/default/#{@new_resource.source}" template_cache_name = "#{cookbook_name}_#{@new_resource.source}" - + if @new_resource.local cache_file_name = @new_resource.source elsif Chef::Config[:solo] cache_file_name = solo_cache_file_name else raw_template_file = fetch_template_via_rest(cache_file_name, template_cache_name) - end - + end + if template_updated? Chef::Log.debug("Updating template for #{@new_resource} in the cache") Chef::FileCache.move_to(raw_template_file.path, cache_file_name) end - template_file = render_with_context(cache_file_name) + render_with_context(cache_file_name) do |template_file| - update = false - - if ::File.exists?(@new_resource.path) - @new_resource.checksum(self.checksum(template_file.path)) - if @new_resource.checksum != @current_resource.checksum - Chef::Log.debug("#{@new_resource} changed from #{@current_resource.checksum} to #{@new_resource.checksum}") - Chef::Log.info("Updating #{@new_resource} at #{@new_resource.path}") + update = false + + if ::File.exists?(@new_resource.path) + @new_resource.checksum(checksum(template_file.path)) + if @new_resource.checksum != @current_resource.checksum + Chef::Log.debug("#{@new_resource} changed from #{@current_resource.checksum} to #{@new_resource.checksum}") + Chef::Log.info("Updating #{@new_resource} at #{@new_resource.path}") + update = true + end + else + Chef::Log.info("Creating #{@new_resource} at #{@new_resource.path}") update = true end - else - Chef::Log.info("Creating #{@new_resource} at #{@new_resource.path}") - update = true - end - - if update - backup - FileUtils.cp(template_file.path, @new_resource.path) - @new_resource.updated = true - else - Chef::Log.debug("#{@new_resource} is unchanged") + + if update + backup + FileUtils.mv(template_file.path, @new_resource.path) + @new_resource.updated = true + else + Chef::Log.debug("#{@new_resource} is unchanged") + end end - + set_owner if @new_resource.owner != nil set_group if @new_resource.group != nil set_mode if @new_resource.mode != nil end - + def action_create_if_missing if ::File.exists?(@new_resource.path) Chef::Log.debug("Template #{@new_resource} exists, taking no action.") @@ -91,32 +92,32 @@ class Chef action_create end end - + private - + def template_updated @template_updated = true end - + def template_not_updated @template_updated = false end - + def template_updated? @template_updated end - + def cookbook_name @cookbook_name = (@new_resource.cookbook || @new_resource.cookbook_name) end - - def render_with_context(cache_file_name) + + def render_with_context(cache_file_name, &block) context = {} context.merge!(@new_resource.variables) context[:node] = @node - render_template(Chef::FileCache.load(cache_file_name), context) + render_template(Chef::FileCache.load(cache_file_name), context, &block) end - + def solo_cache_file_name filename = find_preferred_file( cookbook_name, @@ -129,32 +130,32 @@ class Chef Chef::Log.debug("Using local file for template:#{filename}") Pathname.new(filename).relative_path_from(Pathname.new(Chef::Config[:file_cache_path])).to_s end - + def fetch_template_via_rest(cache_file_name, template_cache_name) if @node.run_state[:template_cache].has_key?(template_cache_name) Chef::Log.debug("I have already fetched the template for #{@new_resource} once this run, not checking again.") template_not_updated return false end - + r = Chef::REST.new(Chef::Config[:template_url]) - + current_checksum = nil - + if Chef::FileCache.has_key?(cache_file_name) current_checksum = self.checksum(Chef::FileCache.load(cache_file_name, false)) else Chef::Log.debug("Template #{@new_resource} is not in the template cache") end - + template_url = generate_url( - @new_resource.source, + @new_resource.source, "templates", { :checksum => current_checksum } ) - + begin raw_template_file = r.get_rest(template_url, true) template_updated @@ -165,13 +166,13 @@ class Chef raise e end end - + # We have checked the cache for this template this run @node.run_state[:template_cache][template_cache_name] = true - + raw_template_file end - + end end end diff --git a/chef/lib/chef/providers.rb b/chef/lib/chef/providers.rb new file mode 100644 index 0000000000..02168b2dcd --- /dev/null +++ b/chef/lib/chef/providers.rb @@ -0,0 +1,80 @@ +# +# Author:: Daniel DeLeo (<dan@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 'chef/provider/breakpoint' +require 'chef/provider/cron' +require 'chef/provider/deploy' +require 'chef/provider/directory' +require 'chef/provider/erl_call' +require 'chef/provider/execute' +require 'chef/provider/file' +require 'chef/provider/git' +require 'chef/provider/group' +require 'chef/provider/http_request' +require 'chef/provider/ifconfig' +require 'chef/provider/link' +require 'chef/provider/log' +require 'chef/provider/mdadm' +require 'chef/provider/mount' +require 'chef/provider/package' +require 'chef/provider/remote_directory' +require 'chef/provider/remote_file' +require 'chef/provider/route' +require 'chef/provider/ruby_block' +require 'chef/provider/script' +require 'chef/provider/service' +require 'chef/provider/subversion' +require 'chef/provider/template' +require 'chef/provider/user' + +require 'chef/provider/package/apt' +require 'chef/provider/package/dpkg' +require 'chef/provider/package/easy_install' +require 'chef/provider/package/freebsd' +require 'chef/provider/package/macports' +require 'chef/provider/package/pacman' +require 'chef/provider/package/portage' +require 'chef/provider/package/rpm' +require 'chef/provider/package/rubygems' +require 'chef/provider/package/yum' +require 'chef/provider/package/zypper' + +require 'chef/provider/service/arch' +require 'chef/provider/service/debian' +require 'chef/provider/service/freebsd' +require 'chef/provider/service/gentoo' +require 'chef/provider/service/init' +require 'chef/provider/service/redhat' +require 'chef/provider/service/simple' +require 'chef/provider/service/upstart' +require 'chef/provider/service/windows' + +require 'chef/provider/user/dscl' +require 'chef/provider/user/pw' +require 'chef/provider/user/useradd' + +require 'chef/provider/group/dscl' +require 'chef/provider/group/gpasswd' +require 'chef/provider/group/groupadd' +require 'chef/provider/group/pw' +require 'chef/provider/group/usermod' + +require 'chef/provider/mount/mount' + +require 'chef/provider/deploy/revision' +require 'chef/provider/deploy/timestamped' diff --git a/chef/lib/chef/recipe.rb b/chef/lib/chef/recipe.rb index d9a2362889..da6d0c0a05 100644 --- a/chef/lib/chef/recipe.rb +++ b/chef/lib/chef/recipe.rb @@ -17,15 +17,13 @@ # limitations under the License. # -require 'chef/resource' -Dir[File.join(File.dirname(__FILE__), 'resource/**/*.rb')].sort.each { |lib| require lib } + +require 'chef/mixin/recipe_definition_dsl_core' require 'chef/mixin/from_file' require 'chef/mixin/language' require 'chef/mixin/language_include_recipe' -require 'chef/mixin/recipe_definition_dsl_core' require 'chef/resource_collection' require 'chef/cookbook_loader' -require 'chef/rest' class Chef class Recipe diff --git a/chef/lib/chef/resource.rb b/chef/lib/chef/resource.rb index 89f4513576..fc95abd595 100644 --- a/chef/lib/chef/resource.rb +++ b/chef/lib/chef/resource.rb @@ -80,7 +80,7 @@ class Chef prior_resource = @collection.lookup(self.to_s) Chef::Log.debug("Setting #{self.to_s} to the state of the prior #{self.to_s}") prior_resource.instance_variables.each do |iv| - unless iv == "@source_line" || iv == "@action" + unless iv.to_sym == :@source_line || iv.to_sym == :@action self.instance_variable_set(iv, prior_resource.instance_variable_get(iv)) end end @@ -162,10 +162,18 @@ class Chef if args.size > 1 notifies_helper(*args) else - resources_array = *args - resources_array.each do |resource| - resource.each do |key, value| - notifies_helper(value[0], key, value[1]) + # This syntax is so weird. surely people will just give us one hash? + notifications = args.flatten + notifications.each do |resources_notifications| + begin + resources_notifications.each do |resource, notification| + Chef::Log.error "resource KV: `#{resource.inspect}' => `#{notification.inspect}'" + notifies_helper(notification[0], resource, notification[1]) + end + rescue NoMethodError + Chef::Log.fatal("encountered NME processing resource #{resources_notifications.inspect}") + Chef::Log.fatal("incoming args: #{args.inspect}") + raise end end end @@ -193,7 +201,11 @@ class Chef end def is(*args) - return *args + if args.size == 1 + args.first + else + return *args + end end def to_s @@ -218,7 +230,9 @@ class Chef def to_hash instance_vars = Hash.new self.instance_variables.each do |iv| - instance_vars[iv.sub(/^@/,'').to_sym] = self.instance_variable_get(iv) unless iv == "@collection" + iv = iv.to_s + next if iv == "@collection" + instance_vars[iv.sub(/^@/,'').to_sym] = self.instance_variable_get(iv) end instance_vars end diff --git a/chef/lib/chef/resource/cron.rb b/chef/lib/chef/resource/cron.rb index 2da76b62f8..206bf2c540 100644 --- a/chef/lib/chef/resource/cron.rb +++ b/chef/lib/chef/resource/cron.rb @@ -47,7 +47,7 @@ class Chef converted_arg = arg end begin - if Integer(arg) > 59 then raise RangeError end + if integerize(arg) > 59 then raise RangeError end rescue ArgumentError end set_or_return( @@ -64,7 +64,7 @@ class Chef converted_arg = arg end begin - if Integer(arg) > 23 then raise RangeError end + if integerize(arg) > 23 then raise RangeError end rescue ArgumentError end set_or_return( @@ -81,7 +81,7 @@ class Chef converted_arg = arg end begin - if Integer(arg) > 31 then raise RangeError end + if integerize(arg) > 31 then raise RangeError end rescue ArgumentError end set_or_return( @@ -98,7 +98,7 @@ class Chef converted_arg = arg end begin - if Integer(arg) > 12 then raise RangeError end + if integerize(arg) > 12 then raise RangeError end rescue ArgumentError end set_or_return( @@ -115,7 +115,7 @@ class Chef converted_arg = arg end begin - if Integer(arg) > 7 then raise RangeError end + if integerize(arg) > 7 then raise RangeError end rescue ArgumentError end set_or_return( @@ -172,6 +172,15 @@ class Chef :kind_of => String ) end + + private + + # On Ruby 1.8, Kernel#Integer will happily do this for you. On 1.9, no. + def integerize(integerish) + Integer(integerish) + rescue TypeError + 0 + end end end end diff --git a/chef/lib/chef/resource/deploy.rb b/chef/lib/chef/resource/deploy.rb index 327f9425c8..a513b16411 100644 --- a/chef/lib/chef/resource/deploy.rb +++ b/chef/lib/chef/resource/deploy.rb @@ -6,9 +6,9 @@ # 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. @@ -38,20 +38,20 @@ require "chef/resource/scm" class Chef class Resource - + # Deploy: Deploy apps from a source control repository. # # Callbacks: # Callbacks can be a block or a string. If given a block, the code # is evaluated as an embedded recipe, and run at the specified # point in the deploy process. If given a string, the string is taken as - # a path to a callback file/recipe. Paths are evaluated relative to the + # a path to a callback file/recipe. Paths are evaluated relative to the # release directory. Callback files can contain chef code (resources, etc.) # class Deploy < Chef::Resource - + provider_base Chef::Provider::Deploy - + def initialize(name, collection=nil, node=nil) super(name, collection, node) @resource_name = :deploy @@ -74,26 +74,26 @@ class Chef @provider = Chef::Provider::Deploy::Timestamped @allowed_actions.push(:force_deploy, :deploy, :rollback) end - + # where the checked out/cloned code goes def destination @destination ||= shared_path + "/#{@repository_cache}" end - + # where shared stuff goes, i.e., logs, tmp, etc. goes here def shared_path @shared_path ||= @deploy_to + "/shared" end - + # where the deployed version of your code goes def current_path @current_path ||= @deploy_to + "/current" end - + def depth @shallow_clone ? "5" : nil end - + # note: deploy_to is your application "meta-root." def deploy_to(arg=nil) set_or_return( @@ -102,7 +102,7 @@ class Chef :kind_of => [ String ] ) end - + def repo(arg=nil) set_or_return( :repo, @@ -111,7 +111,7 @@ class Chef ) end alias :repository :repo - + def remote(arg=nil) set_or_return( :remote, @@ -119,7 +119,7 @@ class Chef :kind_of => [ String ] ) end - + def role(arg=nil) set_or_return( :role, @@ -127,7 +127,7 @@ class Chef :kind_of => [ String ] ) end - + def restart_command(arg=nil, &block) arg ||= block set_or_return( @@ -137,7 +137,7 @@ class Chef ) end alias :restart :restart_command - + def migrate(arg=nil) set_or_return( :migrate, @@ -145,7 +145,7 @@ class Chef :kind_of => [ TrueClass, FalseClass ] ) end - + def migration_command(arg=nil) set_or_return( :migration_command, @@ -153,7 +153,7 @@ class Chef :kind_of => [ String ] ) end - + def user(arg=nil) set_or_return( :user, @@ -161,15 +161,15 @@ class Chef :kind_of => [ String ] ) end - + def group(arg=nil) set_or_return( :group, arg, :kind_of => [ String ] ) - end - + end + def enable_submodules(arg=nil) set_or_return( :enable_submodules, @@ -177,7 +177,7 @@ class Chef :kind_of => [ TrueClass, FalseClass ] ) end - + def shallow_clone(arg=nil) set_or_return( :shallow_clone, @@ -193,7 +193,7 @@ class Chef :kind_of => [ String ] ) end - + def copy_exclude(arg=nil) set_or_return( :copy_exclude, @@ -201,7 +201,7 @@ class Chef :kind_of => [ String ] ) end - + def revision(arg=nil) set_or_return( :revision, @@ -210,7 +210,7 @@ class Chef ) end alias :branch :revision - + def git_ssh_wrapper(arg=nil) set_or_return( :git_ssh_wrapper, @@ -219,7 +219,7 @@ class Chef ) end alias :ssh_wrapper :git_ssh_wrapper - + def svn_username(arg=nil) set_or_return( :svn_username, @@ -227,7 +227,7 @@ class Chef :kind_of => [ String ] ) end - + def svn_password(arg=nil) set_or_return( :svn_password, @@ -235,7 +235,7 @@ class Chef :kind_of => [ String ] ) end - + def svn_arguments(arg=nil) set_or_return( :svn_arguments, @@ -243,7 +243,14 @@ class Chef :kind_of => [ String ] ) end - + + def svn_info_args(arg=nil) + set_or_return( + :svn_arguments, + arg, + :kind_of => [ String ]) + end + def scm_provider(arg=nil) klass = if arg.kind_of?(String) || arg.kind_of?(Symbol) lookup_provider_constant(arg) @@ -256,7 +263,7 @@ class Chef :kind_of => [ Class ] ) end - + def svn_force_export(arg=nil) set_or_return( :svn_force_export, @@ -264,7 +271,7 @@ class Chef :kind_of => [ TrueClass, FalseClass ] ) end - + def environment(arg=nil) if arg.is_a?(String) Chef::Log.info "Setting RAILS_ENV, RACK_ENV, and MERB_ENV to `#{arg}'" @@ -277,8 +284,8 @@ class Chef :kind_of => [ Hash ] ) end - - # An array of paths, relative to your app's root, to be purged from a + + # An array of paths, relative to your app's root, to be purged from a # SCM clone/checkout before symlinking. Use this to get rid of files and # directories you want to be shared between releases. # Default: ["log", "tmp/pids", "public/system"] @@ -289,12 +296,12 @@ class Chef :kind_of => Array ) end - + # An array of paths, relative to your app's root, where you expect dirs to # exist before symlinking. This runs after #purge_before_symlink, so you # can use this to recreate dirs that you had previously purged. - # For example, if you plan to use a shared directory for pids, and you - # want it to be located in $APP_ROOT/tmp/pids, you could purge tmp, + # For example, if you plan to use a shared directory for pids, and you + # want it to be located in $APP_ROOT/tmp/pids, you could purge tmp, # then specify tmp here so that the tmp directory will exist when you # symlink the pids directory in to the current release. # Default: ["tmp", "public", "config"] @@ -305,10 +312,10 @@ class Chef :kind_of => Array ) end - + # A Hash of shared/dir/path => release/dir/path. This attribute determines # which files and dirs in the shared directory get symlinked to the current - # release directory, and where they go. If you have a directory + # release directory, and where they go. If you have a directory # $shared/pids that you would like to symlink as $current_release/tmp/pids # you specify it as "pids" => "tmp/pids" # Default {"system" => "public/system", "pids" => "tmp/pids", "log" => "log"} @@ -319,8 +326,8 @@ class Chef :kind_of => Hash ) end - - # A Hash of shared/dir/path => release/dir/path. This attribute determines + + # A Hash of shared/dir/path => release/dir/path. This attribute determines # which files in the shared directory get symlinked to the current release # directory and where they go. Unlike map_shared_files, these are symlinked # *before* any migration is run. @@ -334,31 +341,31 @@ class Chef :kind_of => Hash ) end - + # Callback fires before migration is run. def before_migrate(arg=nil, &block) arg ||= block set_or_return(:before_migrate, arg, :kind_of => [Proc, String]) end - + # Callback fires before symlinking def before_symlink(arg=nil, &block) arg ||= block set_or_return(:before_symlink, arg, :kind_of => [Proc, String]) end - + # Callback fires before restart def before_restart(arg=nil, &block) arg ||= block set_or_return(:before_restart, arg, :kind_of => [Proc, String]) end - + # Callback fires after restart def after_restart(arg=nil, &block) arg ||= block set_or_return(:after_restart, arg, :kind_of => [Proc, String]) end - + end end end diff --git a/chef/lib/chef/resource/execute.rb b/chef/lib/chef/resource/execute.rb index 3f2ba07547..46b7287d6a 100644 --- a/chef/lib/chef/resource/execute.rb +++ b/chef/lib/chef/resource/execute.rb @@ -100,7 +100,7 @@ class Chef set_or_return( :returns, arg, - :kind_of => [ Integer ] + :kind_of => [ Integer, Array ] ) end diff --git a/chef/lib/chef/resource/mount.rb b/chef/lib/chef/resource/mount.rb index affedf333c..56dcfedee9 100644 --- a/chef/lib/chef/resource/mount.rb +++ b/chef/lib/chef/resource/mount.rb @@ -28,7 +28,7 @@ class Chef @mount_point = name @device = nil @device_type = :device - @fstype = nil + @fstype = "auto" @options = ["defaults"] @dump = 0 @pass = 2 diff --git a/chef/lib/chef/resource/remote_directory.rb b/chef/lib/chef/resource/remote_directory.rb index 9742df5540..1920c086de 100644 --- a/chef/lib/chef/resource/remote_directory.rb +++ b/chef/lib/chef/resource/remote_directory.rb @@ -6,9 +6,9 @@ # 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. @@ -21,7 +21,7 @@ require 'chef/resource/directory' class Chef class Resource class RemoteDirectory < Chef::Resource::Directory - + def initialize(name, collection=nil, node=nil) super(name, collection, node) @resource_name = :remote_directory @@ -30,6 +30,7 @@ class Chef @delete = false @action = :create @recursive = true + @purge = false @files_backup = 5 @files_owner = nil @files_group = nil @@ -37,7 +38,7 @@ class Chef @allowed_actions.push(:create, :delete) @cookbook = nil end - + def source(args=nil) set_or_return( :source, @@ -45,7 +46,7 @@ class Chef :kind_of => String ) end - + def files_backup(arg=nil) set_or_return( :files_backup, @@ -53,7 +54,15 @@ class Chef :kind_of => [ Integer, FalseClass ] ) end - + + def purge(arg=nil) + set_or_return( + :purge, + arg, + :kind_of => [ TrueClass, FalseClass ] + ) + end + def files_group(arg=nil) set_or_return( :files_group, @@ -61,7 +70,7 @@ class Chef :regex => [ /^([a-z]|[A-Z]|[0-9]|_|-)+$/, /^\d+$/ ] ) end - + def files_mode(arg=nil) set_or_return( :files_mode, @@ -69,7 +78,7 @@ class Chef :regex => /^\d{3,4}$/ ) end - + def files_owner(arg=nil) set_or_return( :files_owner, @@ -77,7 +86,7 @@ class Chef :regex => [ /^([a-z]|[A-Z]|[0-9]|_|-)+$/, /^\d+$/ ] ) end - + def cookbook(args=nil) set_or_return( :cookbook, @@ -85,7 +94,7 @@ class Chef :kind_of => String ) end - + end end end diff --git a/chef/lib/chef/resource/scm.rb b/chef/lib/chef/resource/scm.rb index d444d7f6d6..215fb59d06 100644 --- a/chef/lib/chef/resource/scm.rb +++ b/chef/lib/chef/resource/scm.rb @@ -6,9 +6,9 @@ # 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. @@ -22,7 +22,7 @@ require 'chef/resource' class Chef class Resource class Scm < Chef::Resource - + def initialize(name, collection=nil, node=nil) super(name, collection, node) @destination = name @@ -34,7 +34,7 @@ class Chef @depth = nil @allowed_actions.push(:checkout, :export, :sync, :diff, :log) end - + def destination(arg=nil) set_or_return( :destination, @@ -42,7 +42,7 @@ class Chef :kind_of => String ) end - + def repository(arg=nil) set_or_return( :repository, @@ -50,7 +50,7 @@ class Chef :kind_of => String ) end - + def revision(arg=nil) set_or_return( :revision, @@ -58,7 +58,7 @@ class Chef :kind_of => String ) end - + def user(arg=nil) set_or_return( :user, @@ -66,7 +66,7 @@ class Chef :kind_of => [String, Integer] ) end - + def group(arg=nil) set_or_return( :group, @@ -74,7 +74,7 @@ class Chef :kind_of => [String, Integer] ) end - + def svn_username(arg=nil) set_or_return( :svn_username, @@ -82,7 +82,7 @@ class Chef :kind_of => String ) end - + def svn_password(arg=nil) set_or_return( :svn_password, @@ -90,7 +90,7 @@ class Chef :kind_of => String ) end - + def svn_arguments(arg=nil) set_or_return( :svn_arguments, @@ -98,7 +98,14 @@ class Chef :kind_of => String ) end - + + def svn_info_args(arg=nil) + set_or_return( + :svn_arguments, + arg, + :kind_of => String) + end + # Capistrano and git-deploy use ``shallow clone'' def depth(arg=nil) set_or_return( @@ -107,7 +114,7 @@ class Chef :kind_of => Integer ) end - + def enable_submodules(arg=nil) set_or_return( :enable_submodules, @@ -115,7 +122,7 @@ class Chef :kind_of => [TrueClass, FalseClass] ) end - + def remote(arg=nil) set_or_return( :remote, @@ -123,7 +130,7 @@ class Chef :kind_of => String ) end - + def ssh_wrapper(arg=nil) set_or_return( :ssh_wrapper, @@ -131,7 +138,7 @@ class Chef :kind_of => String ) end - + end end end
\ No newline at end of file diff --git a/chef/lib/chef/resource/service.rb b/chef/lib/chef/resource/service.rb index 1ca5ba6e71..f32d3c062b 100644 --- a/chef/lib/chef/resource/service.rb +++ b/chef/lib/chef/resource/service.rb @@ -35,6 +35,7 @@ class Chef @restart_command = nil @reload_command = nil @action = "nothing" + @startup_type = :automatic @supports = { :restart => false, :reload => false, :status => false } @allowed_actions.push(:enable, :disable, :start, :stop, :restart, :reload) end @@ -127,7 +128,15 @@ class Chef @supports end end - + + # This attribute applies for Windows only. + def startup_type(arg=nil) + set_or_return( + :startup_type, + arg, + :equal_to => [:automatic, :mannual] + ) + end end end diff --git a/chef/lib/chef/resources.rb b/chef/lib/chef/resources.rb new file mode 100644 index 0000000000..8577c1b843 --- /dev/null +++ b/chef/lib/chef/resources.rb @@ -0,0 +1,60 @@ +# +# Author:: Daniel DeLeo (<dan@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 'chef/resource/apt_package' +require 'chef/resource/bash' +require 'chef/resource/breakpoint' +require 'chef/resource/cron' +require 'chef/resource/csh' +require 'chef/resource/deploy' +require 'chef/resource/deploy_revision' +require 'chef/resource/directory' +require 'chef/resource/dpkg_package' +require 'chef/resource/easy_install_package' +require 'chef/resource/erl_call' +require 'chef/resource/execute' +require 'chef/resource/file' +require 'chef/resource/freebsd_package' +require 'chef/resource/gem_package' +require 'chef/resource/git' +require 'chef/resource/group' +require 'chef/resource/http_request' +require 'chef/resource/ifconfig' +require 'chef/resource/link' +require 'chef/resource/log' +require 'chef/resource/macports_package' +require 'chef/resource/mdadm' +require 'chef/resource/mount' +require 'chef/resource/package' +require 'chef/resource/pacman_package' +require 'chef/resource/perl' +require 'chef/resource/portage_package' +require 'chef/resource/python' +require 'chef/resource/remote_directory' +require 'chef/resource/remote_file' +require 'chef/resource/route' +require 'chef/resource/ruby' +require 'chef/resource/ruby_block' +require 'chef/resource/scm' +require 'chef/resource/script' +require 'chef/resource/service' +require 'chef/resource/subversion' +require 'chef/resource/template' +require 'chef/resource/timestamped_deploy' +require 'chef/resource/user' +require 'chef/resource/yum_package' diff --git a/chef/lib/chef/rest.rb b/chef/lib/chef/rest.rb index 9e214a1ddb..d8723ae71a 100644 --- a/chef/lib/chef/rest.rb +++ b/chef/lib/chef/rest.rb @@ -10,9 +10,9 @@ # 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. @@ -20,67 +20,58 @@ # limitations under the License. # -require 'chef/mixin/params_validate' require 'net/https' require 'uri' require 'json' require 'tempfile' -require 'singleton' -require 'mixlib/authentication/signedheaderauth' require 'chef/api_client' - -include Mixlib::Authentication::SignedHeaderAuth +require 'chef/rest/auth_credentials' +require 'chef/rest/rest_request' class Chef class REST + attr_reader :auth_credentials + attr_accessor :url, :cookies, :sign_on_redirect, :redirect_limit - class CookieJar < Hash - include Singleton - end - - attr_accessor :url, :cookies, :client_name, :signing_key, :signing_key_filename, :sign_on_redirect, :sign_request - def initialize(url, client_name=Chef::Config[:node_name], signing_key_filename=Chef::Config[:client_key], options={}) @url = url @cookies = CookieJar.instance - @client_name = client_name @default_headers = options[:headers] || {} - if signing_key_filename - @signing_key_filename = signing_key_filename - @signing_key = load_signing_key(signing_key_filename) - @sign_request = true - else - @signing_key = nil - @sign_request = false - end - @sign_on_redirect = true + @auth_credentials = AuthCredentials.new(client_name, signing_key_filename) + @sign_on_redirect, @sign_request = true, true + @redirects_followed = 0 + @redirect_limit = 10 end - def load_signing_key(key) - begin - IO.read(key) - rescue StandardError=>se - Chef::Log.error "Failed to read the private key #{key}: #{se.inspect}, #{se.backtrace}" - raise Chef::Exceptions::PrivateKeyMissing, "I cannot read #{key}, which you told me to use to sign requests!" - end + def signing_key_filename + @auth_credentials.key_file + end + + def client_name + @auth_credentials.client_name + end + + def signing_key + @auth_credentials.raw_key end - - # Register the client - def register(name=Chef::Config[:node_name], destination=Chef::Config[:client_key]) - raise Chef::Exceptions::CannotWritePrivateKey, "I cannot write your private key to #{destination} - check permissions?" if (File.exists?(destination) && !File.writable?(destination)) + # Register the client + def register(name=Chef::Config[:node_name], destination=Chef::Config[:client_key]) + if (File.exists?(destination) && !File.writable?(destination)) + raise Chef::Exceptions::CannotWritePrivateKey, "I cannot write your private key to #{destination} - check permissions?" + end nc = Chef::ApiClient.new nc.name(name) catch(:done) do - retries = Chef::Config[:client_registration_retries] || 5 + retries = config[:client_registration_retries] || 5 0.upto(retries) do |n| begin response = nc.save(true, true) Chef::Log.debug("Registration response: #{response.inspect}") raise Chef::Exceptions::CannotWritePrivateKey, "The response from the server did not include a private key!" unless response.has_key?("private_key") # Write out the private key - file = File.open(destination, File::WRONLY|File::EXCL|File::CREAT, 0600) + file = ::File.open(destination, File::WRONLY|File::EXCL|File::CREAT, 0600) file.print(response["private_key"]) file.close throw :done @@ -100,27 +91,40 @@ class Chef # # === Parameters # path:: The path to GET - # raw:: Whether you want the raw body returned, or JSON inflated. Defaults + # raw:: Whether you want the raw body returned, or JSON inflated. Defaults # to JSON inflated. def get_rest(path, raw=false, headers={}) - run_request(:GET, create_url(path), headers, false, 10, raw) - end - + if raw + streaming_request(create_url(path), headers) + else + api_request(:GET, create_url(path), headers) + end + end + # Send an HTTP DELETE request to the path - def delete_rest(path, headers={}) - run_request(:DELETE, create_url(path), headers) - end - - # Send an HTTP POST request to the path + def delete_rest(path, headers={}) + api_request(:DELETE, create_url(path), headers) + end + + # Send an HTTP POST request to the path def post_rest(path, json, headers={}) - run_request(:POST, create_url(path), headers, json) - end - + api_request(:POST, create_url(path), headers, json) + end + # Send an HTTP PUT request to the path def put_rest(path, json, headers={}) - run_request(:PUT, create_url(path), headers, json) + api_request(:PUT, create_url(path), headers, json) end - + + # Streams a download to a tempfile, then yields the tempfile to a block. + # After the download, the tempfile will be closed and unlinked. + # If you rename the tempfile, it will not be deleted. + # Beware that if the server streams infinite content, this method will + # stream it until you run out of disk space. + def fetch(path, headers={}) + streaming_request(create_url(path), headers) {|tmp_file| yield tmp_file } + end + def create_url(path) if path =~ /^(http|https):\/\// URI.parse(path) @@ -128,150 +132,39 @@ class Chef URI.parse("#{@url}/#{path}") end end - - def sign_request(http_method, path, private_key, user_id, body = "", host="localhost") - #body = "" if body == false - timestamp = Time.now.utc.iso8601 - sign_obj = Mixlib::Authentication::SignedHeaderAuth.signing_object( - :http_method=>http_method, - :path => path, - :body=>body, - :user_id=>user_id, - :timestamp=>timestamp) - signed = sign_obj.sign(private_key).merge({:host => host}) - signed.inject({}){|memo, kv| memo["#{kv[0].to_s.upcase}"] = kv[1];memo} + + def sign_requests? + auth_credentials.sign_requests? && @sign_request end - + # Actually run an HTTP request. First argument is the HTTP method, # which should be one of :GET, :PUT, :POST or :DELETE. Next is the # URL, then an object to include in the body (which will be converted with - # .to_json) and finally, the limit of HTTP Redirects to follow (10). + # .to_json). The limit argument is unused, it is present for backwards + # compatibility. Configure the redirect limit with #redirect_limit= + # instead. # # Typically, you won't use this method -- instead, you'll use one of # the helper methods (get_rest, post_rest, etc.) # # Will return the body of the response on success. - def run_request(method, url, headers={}, data=false, limit=10, raw=false) - - http_retry_delay = Chef::Config[:http_retry_delay] - http_retry_count = Chef::Config[:http_retry_count] - - raise ArgumentError, 'HTTP redirect too deep' if limit == 0 - - http = Net::HTTP.new(url.host, url.port) - if url.scheme == "https" - http.use_ssl = true - if Chef::Config[:ssl_verify_mode] == :verify_none - http.verify_mode = OpenSSL::SSL::VERIFY_NONE - elsif Chef::Config[:ssl_verify_mode] == :verify_peer - http.verify_mode = OpenSSL::SSL::VERIFY_PEER - end - if Chef::Config[:ssl_ca_path] and File.exists?(Chef::Config[:ssl_ca_path]) - http.ca_path = Chef::Config[:ssl_ca_path] - elsif Chef::Config[:ssl_ca_file] and File.exists?(Chef::Config[:ssl_ca_file]) - http.ca_file = Chef::Config[:ssl_ca_file] - end - if Chef::Config[:ssl_client_cert] && File.exists?(Chef::Config[:ssl_client_cert]) - http.cert = OpenSSL::X509::Certificate.new(File.read(Chef::Config[:ssl_client_cert])) - http.key = OpenSSL::PKey::RSA.new(File.read(Chef::Config[:ssl_client_key])) - end - end - - http.read_timeout = Chef::Config[:rest_timeout] - - headers = @default_headers.merge(headers) - - unless raw - headers = headers.merge({ - 'Accept' => "application/json", - }) - end + def run_request(method, url, headers={}, data=false, limit=nil, raw=false) + json_body = data ? data.to_json : nil + headers = build_headers(method, url, headers, json_body, raw) - headers['X-Chef-Version'] = ::Chef::VERSION - - if @cookies.has_key?("#{url.host}:#{url.port}") - headers['Cookie'] = @cookies["#{url.host}:#{url.port}"] - end - - json_body = data ? data.to_json : nil - - if @sign_request - raise ArgumentError, "Cannot sign the request without a client name, check that :node_name is assigned" if @client_name.nil? - Chef::Log.debug("Signing the request as #{@client_name}") - if json_body - headers.merge!(sign_request(method, url.path, OpenSSL::PKey::RSA.new(@signing_key), @client_name, json_body, "#{url.host}:#{url.port}")) - else - headers.merge!(sign_request(method, url.path, OpenSSL::PKey::RSA.new(@signing_key), @client_name, "", "#{url.host}:#{url.port}")) - end - end - - req = nil - case method - when :GET - req_path = "#{url.path}" - req_path << "?#{url.query}" if url.query - req = Net::HTTP::Get.new(req_path, headers) - when :POST - headers["Content-Type"] = 'application/json' if data - req_path = "#{url.path}" - req_path << "?#{url.query}" if url.query - req = Net::HTTP::Post.new(req_path, headers) - req.body = json_body if json_body - when :PUT - headers["Content-Type"] = 'application/json' if data - req_path = "#{url.path}" - req_path << "?#{url.query}" if url.query - req = Net::HTTP::Put.new(req_path, headers) - req.body = json_body if json_body - when :DELETE - req_path = "#{url.path}" - req_path << "?#{url.query}" if url.query - req = Net::HTTP::Delete.new(req_path, headers) - else - raise ArgumentError, "You must provide :GET, :PUT, :POST or :DELETE as the method" - end - - Chef::Log.debug("Sending HTTP Request via #{req.method} to #{url.host}:#{url.port}#{req.path}") - - # Optionally handle HTTP Basic Authentication - req.basic_auth(url.user, url.password) if url.user - - res = nil tf = nil - http_attempts = 0 - begin - http_attempts += 1 - - res = http.request(req) do |response| + retriable_rest_request(method, url, json_body, headers) do |rest_request| + + res = rest_request.call do |response| if raw - tf = Tempfile.new("chef-rest") - # Stolen from http://www.ruby-forum.com/topic/166423 - # Kudos to _why! - size, total = 0, response.header['Content-Length'].to_i - response.read_body do |chunk| - tf.write(chunk) - size += chunk.size - if size == 0 - Chef::Log.debug("#{req.path} done (0 length file)") - elsif total == 0 - Chef::Log.debug("#{req.path} (zero content length)") - else - Chef::Log.debug("#{req.path}" + " %d%% done (%d of %d)" % [(size * 100) / total, size, total]) - end - end - tf.close - tf + tf = stream_to_tempfile(url, response) else response.read_body end - response end - + if res.kind_of?(Net::HTTPSuccess) - if res['set-cookie'] - @cookies["#{url.host}:#{url.port}"] = res['set-cookie'] - end if res['content-type'] =~ /json/ response_body = res.body.chomp JSON.parse(response_body) @@ -283,37 +176,114 @@ class Chef end end elsif res.kind_of?(Net::HTTPFound) or res.kind_of?(Net::HTTPMovedPermanently) - if res['set-cookie'] - @cookies["#{url.host}:#{url.port}"] = res['set-cookie'] - end - @sign_request = false if @sign_on_redirect == false - run_request(:GET, create_url(res['location']), {}, false, limit - 1, raw) + follow_redirect {run_request(:GET, create_url(res['location']), {}, false, nil, raw)} else if res['content-type'] =~ /json/ exception = JSON.parse(res.body) - Chef::Log.debug("HTTP Request Returned #{res.code} #{res.message}: #{exception["error"].respond_to?(:join) ? exception["error"].join(", ") : exception["error"]}") + msg = "HTTP Request Returned #{res.code} #{res.message}: " + msg << (exception["error"].respond_to?(:join) ? exception["error"].join(", ") : exception["error"].to_s) + Chef::Log.warn(msg) end res.error! end - + end + end + + # Similar to #run_request but only supports JSON APIs. File Download not supported. + def api_request(method, url, headers={}, data=false) + json_body = data ? data.to_json : nil + headers = build_headers(method, url, headers, json_body) + + retriable_rest_request(method, url, json_body, headers) do |rest_request| + response = rest_request.call {|r| r.read_body} + + if response.kind_of?(Net::HTTPSuccess) + if response['content-type'] =~ /json/ + JSON.parse(response.body.chomp) + else + Chef::Log.warn("Expected JSON response, but got content-type '#{response['content-type']}'") + response.body + end + elsif redirect_location = redirected_to(response) + follow_redirect {api_request(:GET, create_url(redirect_location))} + else + if response['content-type'] =~ /json/ + exception = JSON.parse(response.body) + msg = "HTTP Request Returned #{response.code} #{response.message}: " + msg << (exception["error"].respond_to?(:join) ? exception["error"].join(", ") : exception["error"].to_s) + Chef::Log.warn(msg) + end + response.error! + end + end + end + + # similar to #run_request but only supports streaming downloads. + # Only supports GET, doesn't speak JSON + # Streams the response body to a tempfile. If a block is given, it's + # passed to the tempfile, which means that the tempfile will automatically + # be unlinked after the block is executed. + # If no block is given, the tempfile is returned, which means it's up to + # you to unlink the tempfile when you're done with it. + def streaming_request(url, headers, &block) + headers = build_headers(:GET, url, headers, nil, true) + retriable_rest_request(:GET, url, nil, headers) do |rest_request| + tempfile = nil + response = rest_request.call do |r| + if block_given? && r.kind_of?(Net::HTTPSuccess) + begin + tempfile = stream_to_tempfile(url, r, &block) + yield tempfile + ensure + tempfile.close! + end + else + tempfile = stream_to_tempfile(url, r) + end + end + if response.kind_of?(Net::HTTPSuccess) + tempfile + elsif redirect_location = redirected_to(response) + # TODO: test tempfile unlinked when following redirects. + tempfile && tempfile.close! + follow_redirect {streaming_request(create_url(redirect_location), {}, &block)} + else + tempfile && tempfile.close! + response.error! + end + end + end + + def retriable_rest_request(method, url, req_body, headers) + rest_request = Chef::REST::RESTRequest.new(method, url, req_body, headers) + + Chef::Log.debug("Sending HTTP Request via #{method} to #{url.host}:#{url.port}#{rest_request.path}") + + http_attempts = 0 + + begin + http_attempts += 1 + + res = yield rest_request + rescue Errno::ECONNREFUSED if http_retry_count - http_attempts + 1 > 0 - Chef::Log.error("Connection refused connecting to #{url.host}:#{url.port} for #{req.path}, retry #{http_attempts}/#{http_retry_count}") + Chef::Log.error("Connection refused connecting to #{url.host}:#{url.port} for #{rest_request.path}, retry #{http_attempts}/#{http_retry_count}") sleep(http_retry_delay) retry end - raise Errno::ECONNREFUSED, "Connection refused connecting to #{url.host}:#{url.port} for #{req.path}, giving up" + raise Errno::ECONNREFUSED, "Connection refused connecting to #{url.host}:#{url.port} for #{rest_request.path}, giving up" rescue Timeout::Error if http_retry_count - http_attempts + 1 > 0 - Chef::Log.error("Timeout connecting to #{url.host}:#{url.port} for #{req.path}, retry #{http_attempts}/#{http_retry_count}") + Chef::Log.error("Timeout connecting to #{url.host}:#{url.port} for #{rest_request.path}, retry #{http_attempts}/#{http_retry_count}") sleep(http_retry_delay) retry end - raise Timeout::Error, "Timeout connecting to #{url.host}:#{url.port} for #{req.path}, giving up" + raise Timeout::Error, "Timeout connecting to #{url.host}:#{url.port} for #{rest_request.path}, giving up" rescue Net::HTTPServerException if res.kind_of?(Net::HTTPForbidden) if http_retry_count - http_attempts + 1 > 0 - Chef::Log.error("Received 403 Forbidden against #{url.host}:#{url.port} for #{req.path}, retry #{http_attempts}/#{http_retry_count}") + Chef::Log.error("Received 403 Forbidden against #{url.host}:#{url.port} for #{rest_request.path}, retry #{http_attempts}/#{http_retry_count}") sleep(http_retry_delay) retry end @@ -321,6 +291,81 @@ class Chef raise end end - + + def authentication_headers(method, url, json_body=nil) + request_params = {:http_method => method, :path => url.path, :body => json_body, :host => "#{url.host}:#{url.port}"} + request_params[:body] ||= "" + auth_credentials.signature_headers(request_params) + end + + def http_retry_delay + config[:http_retry_delay] + end + + def http_retry_count + config[:http_retry_count] + end + + def config + Chef::Config + end + + def follow_redirect + raise Chef::Exceptions::RedirectLimitExceeded if @redirects_followed >= redirect_limit + @redirects_followed += 1 + Chef::Log.debug("Following redirect #{@redirects_followed}/#{redirect_limit}") + if @sign_on_redirect + yield + else + @sign_request = false + yield + end + ensure + @redirects_followed = 0 + @sign_request = true + end + + private + + def redirected_to(response) + if response.kind_of?(Net::HTTPFound) || response.kind_of?(Net::HTTPMovedPermanently) + response['location'] + else + nil + end + end + + def build_headers(method, url, headers={}, json_body=false, raw=false) + headers = @default_headers.merge(headers) + headers['Accept'] = "application/json" unless raw + headers["Content-Type"] = 'application/json' if json_body + headers.merge!(authentication_headers(method, url, json_body)) if sign_requests? + headers + end + + def stream_to_tempfile(url, response) + tf = Tempfile.open("chef-rest") + Chef::Log.debug("Streaming download from #{url.to_s} to tempfile #{tf.path}") + # Stolen from http://www.ruby-forum.com/topic/166423 + # Kudos to _why! + size, total = 0, response.header['Content-Length'].to_i + response.read_body do |chunk| + tf.write(chunk) + size += chunk.size + if size == 0 + Chef::Log.debug("#{url.path} done (0 length file)") + elsif total == 0 + Chef::Log.debug("#{url.path} (zero content length or no Content-Length header)") + else + Chef::Log.debug("#{url.path}" + " %d%% done (%d of %d)" % [(size * 100) / total, size, total]) + end + end + tf.close + tf + rescue Exception + tf.close! + raise + end + end end diff --git a/chef/lib/chef/rest/auth_credentials.rb b/chef/lib/chef/rest/auth_credentials.rb new file mode 100644 index 0000000000..9e2aaf8a24 --- /dev/null +++ b/chef/lib/chef/rest/auth_credentials.rb @@ -0,0 +1,78 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Author:: Thom May (<thom@clearairturbulence.org>) +# Author:: Nuo Yan (<nuo@opscode.com>) +# Author:: Christopher Brown (<cb@opscode.com>) +# Author:: Christopher Walters (<cw@opscode.com>) +# Author:: Daniel DeLeo (<dan@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 'chef/exceptions' +require 'mixlib/authentication/signedheaderauth' + +class Chef + class REST + class AuthCredentials + attr_reader :key_file, :client_name, :key, :raw_key + + def initialize(client_name=nil, key_file=nil) + @client_name, @key_file = client_name, key_file + load_signing_key if sign_requests? + end + + def sign_requests? + !!key_file + end + + def signature_headers(request_params={}) + raise ArgumentError, "Cannot sign the request without a client name, check that :node_name is assigned" if client_name.nil? + Chef::Log.debug("Signing the request as #{client_name}") + + # params_in = {:http_method => :GET, :path => "/clients", :body => "", :host => "localhost"} + request_params = request_params.dup + request_params[:timestamp] = Time.now.utc.iso8601 + request_params[:user_id] = client_name + host = request_params.delete(:host) || "localhost" + + sign_obj = Mixlib::Authentication::SignedHeaderAuth.signing_object(request_params) + signed = sign_obj.sign(key).merge({:host => host}) + signed.inject({}){|memo, kv| memo["#{kv[0].to_s.upcase}"] = kv[1];memo} + end + + private + + def load_signing_key + begin + @raw_key = IO.read(key_file) + rescue SystemCallError, IOError => e + Chef::Log.fatal "Failed to read the private key #{key_file}: #{e.inspect}, #{e.backtrace}" + raise Chef::Exceptions::PrivateKeyMissing, "I cannot read #{key_file}, which you told me to use to sign requests!" + end + assert_valid_key_format!(@raw_key) + @key = OpenSSL::PKey::RSA.new(@raw_key) + end + + def assert_valid_key_format!(raw_key) + unless (raw_key =~ /\A-----BEGIN RSA PRIVATE KEY-----$/) && (raw_key =~ /^-----END RSA PRIVATE KEY-----\Z/) + msg = "The file #{key_file} does not contain a correctly formatted private key.\n" + msg << "The key file should begin with '-----BEGIN RSA PRIVATE KEY-----' and end with '-----END RSA PRIVATE KEY-----'" + raise Chef::Exceptions::InvalidPrivateKey, msg + end + end + + end + end +end diff --git a/chef/lib/chef/application/server.rb b/chef/lib/chef/rest/cookie_jar.rb index a42f61dc1e..e3137708a2 100644 --- a/chef/lib/chef/application/server.rb +++ b/chef/lib/chef/rest/cookie_jar.rb @@ -1,19 +1,31 @@ # -# Author:: AJ Christensen (<aj@opscode.com>) -# Copyright:: Copyright (c) 2008 Opscode, Inc. +# Author:: Adam Jacob (<adam@opscode.com>) +# Author:: Thom May (<thom@clearairturbulence.org>) +# Author:: Nuo Yan (<nuo@opscode.com>) +# Author:: Christopher Brown (<cb@opscode.com>) +# Author:: Christopher Walters (<cw@opscode.com>) +# Author:: Daniel DeLeo (<dan@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 'singleton' -require 'chef/application' - +class Chef + class REST + class CookieJar < Hash + include Singleton + end + end +end diff --git a/chef/lib/chef/rest/rest_request.rb b/chef/lib/chef/rest/rest_request.rb new file mode 100644 index 0000000000..66e2a11233 --- /dev/null +++ b/chef/lib/chef/rest/rest_request.rb @@ -0,0 +1,151 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Author:: Thom May (<thom@clearairturbulence.org>) +# Author:: Nuo Yan (<nuo@opscode.com>) +# Author:: Christopher Brown (<cb@opscode.com>) +# Author:: Christopher Walters (<cw@opscode.com>) +# Author:: Daniel DeLeo (<dan@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 'uri' +require 'net/http' +require 'chef/rest/cookie_jar' + +class Chef + class REST + class RESTRequest + attr_reader :method, :url, :headers, :http_client, :http_request + + def initialize(method, url, req_body, base_headers={}) + @method, @url = method, url + @request_body = nil + @cookies = CookieJar.instance + configure_http_client + build_headers(base_headers) + configure_http_request(req_body) + end + + def host + @url.host + end + + def port + @url.port + end + + def query + @url.query + end + + def path + @url.path.empty? ? "/" : @url.path + end + + def call + http_client.request(http_request) do |response| + store_cookie(response) + yield response if block_given? + response + end + end + + def config + Chef::Config + end + + private + + def store_cookie(response) + if response['set-cookie'] + @cookies["#{host}:#{port}"] = response['set-cookie'] + end + end + + def build_headers(headers) + @headers = headers.dup + # TODO: need to set accept somewhere else + # headers.merge!('Accept' => "application/json") unless raw + @headers['X-Chef-Version'] = ::Chef::VERSION + + if @cookies.has_key?("#{host}:#{port}") + @headers['Cookie'] = @cookies["#{host}:#{port}"] + end + end + + def configure_http_client + @http_client = Net::HTTP.new(host, port) + if url.scheme == "https" + @http_client.use_ssl = true + if config[:ssl_verify_mode] == :verify_none + @http_client.verify_mode = OpenSSL::SSL::VERIFY_NONE + elsif config[:ssl_verify_mode] == :verify_peer + @http_client.verify_mode = OpenSSL::SSL::VERIFY_PEER + end + if config[:ssl_ca_path] + unless ::File.exist?(config[:ssl_ca_path]) + raise Chef::Exceptions::ConfigurationError, "The configured ssl_ca_path #{config[:ssl_ca_path]} does not exist" + end + @http_client.ca_path = config[:ssl_ca_path] + elsif config[:ssl_ca_file] + unless ::File.exist?(config[:ssl_ca_file]) + raise Chef::Exceptions::ConfigurationError, "The configured ssl_ca_file #{config[:ssl_ca_file]} does not exist" + end + @http_client.ca_file = config[:ssl_ca_file] + end + if (config[:ssl_client_cert] || config[:ssl_client_key]) + unless (config[:ssl_client_cert] && config[:ssl_client_key]) + raise Chef::Exceptions::ConfigurationError, "You must configure ssl_client_cert and ssl_client_key together" + end + unless ::File.exists?(config[:ssl_client_cert]) + raise Chef::Exceptions::ConfigurationError, "The configured ssl_client_cert #{config[:ssl_client_cert]} does not exist" + end + unless ::File.exists?(config[:ssl_client_key]) + raise Chef::Exceptions::ConfigurationError, "The configured ssl_client_key #{config[:ssl_client_key]} does not exist" + end + @http_client.cert = OpenSSL::X509::Certificate.new(::File.read(config[:ssl_client_cert])) + @http_client.key = OpenSSL::PKey::RSA.new(::File.read(config[:ssl_client_key])) + end + end + + @http_client.read_timeout = config[:rest_timeout] + end + + + def configure_http_request(request_body=nil) + req_path = "#{path}" + req_path << "?#{query}" if query + + @http_request = case method.to_s.downcase + when "get" + Net::HTTP::Get.new(req_path, headers) + when "post" + Net::HTTP::Post.new(req_path, headers) + when "put" + Net::HTTP::Put.new(req_path, headers) + when "delete" + Net::HTTP::Delete.new(req_path, headers) + else + raise ArgumentError, "You must provide :GET, :PUT, :POST or :DELETE as the method" + end + + @http_request.body = request_body if (request_body && @http_request.request_body_permitted?) + # Optionally handle HTTP Basic Authentication + @http_request.basic_auth(url.user, url.password) if url.user + end + + end + end +end
\ No newline at end of file diff --git a/chef/lib/chef/role.rb b/chef/lib/chef/role.rb index 64ae7609e9..75707477f3 100644 --- a/chef/lib/chef/role.rb +++ b/chef/lib/chef/role.rb @@ -8,9 +8,9 @@ # 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. @@ -28,19 +28,19 @@ require 'extlib' require 'json' class Chef - class Role - + class Role + include Chef::Mixin::FromFile include Chef::Mixin::ParamsValidate include Chef::IndexQueue::Indexable - + DESIGN_DOCUMENT = { "version" => 6, "language" => "javascript", "views" => { "all" => { "map" => <<-EOJS - function(doc) { + function(doc) { if (doc.chef_type == "role") { emit(doc.name, doc); } @@ -49,7 +49,7 @@ class Chef }, "all_id" => { "map" => <<-EOJS - function(doc) { + function(doc) { if (doc.chef_type == "role") { emit(doc.name, doc.name); } @@ -61,14 +61,14 @@ class Chef attr_accessor :couchdb_rev, :couchdb attr_reader :couchdb_id - + # Create a new Chef::Role object. def initialize(couchdb=nil) - @name = '' - @description = '' + @name = '' + @description = '' @default_attributes = Mash.new @override_attributes = Mash.new - @run_list = Chef::RunList.new + @run_list = Chef::RunList.new @couchdb_rev = nil @couchdb_id = nil @couchdb = couchdb || Chef::CouchDB.new @@ -87,7 +87,7 @@ class Chef Chef::REST.new(Chef::Config[:chef_server_url]) end - def name(arg=nil) + def name(arg=nil) set_or_return( :name, arg, @@ -95,7 +95,7 @@ class Chef ) end - def description(arg=nil) + def description(arg=nil) set_or_return( :description, arg, @@ -113,7 +113,7 @@ class Chef # Chef::Log.warn "Chef::Role#recipes method is deprecated. Please use Chef::Role#run_list" # run_list(*args) # end - + def default_attributes(arg=nil) set_or_return( :default_attributes, @@ -144,11 +144,11 @@ class Chef result end - # Serialize this object as a hash + # Serialize this object as a hash def to_json(*a) to_hash.to_json(*a) end - + # Create a Chef::Role from JSON def self.json_create(o) role = new @@ -164,15 +164,15 @@ class Chef role.couchdb_rev = o["_rev"] if o.has_key?("_rev") role.index_id = role.couchdb_id role.couchdb_id = o["_id"] if o.has_key?("_id") - role + role end - + # List all the Chef::Role objects in the CouchDB. If inflate is set to true, you will get # the full list of all Roles, fully inflated. def self.cdb_list(inflate=false, couchdb=nil) rs = (couchdb || Chef::CouchDB.new).list("roles", inflate) lookup = (inflate ? "value" : "key") - rs["rows"].collect { |r| r[lookup] } + rs["rows"].collect { |r| r[lookup] } end # Get the list of all roles from the API. @@ -187,12 +187,12 @@ class Chef chef_server_rest.get_rest("roles") end end - + # Load a role by name from CouchDB def self.cdb_load(name, couchdb=nil) (couchdb || Chef::CouchDB.new).load("role", name) end - + # Load a role by name from the API def self.load(name) chef_server_rest.get_rest("roles/#{name}") @@ -205,44 +205,36 @@ class Chef nil end end - + # Remove this role from the CouchDB def cdb_destroy couchdb.delete("role", @name, couchdb_rev) - if Chef::Config[:couchdb_version] == 0.9 - rs = couchdb.get_view("nodes", "by_run_list", :startkey => "role[#{@name}]", :endkey => "role[#{@name}]", :include_docs => true) - rs["rows"].each do |row| - node = row["doc"] - node.run_list.remove("role[#{@name}]") - node.cdb_save - end - else - Chef::Node.cdb_list.each do |node| - n = Chef::Node.cdb_load(node) - n.run_list.remove("role[#{@name}]") - n.cdb_save - end + rs = couchdb.get_view("nodes", "by_run_list", :startkey => "role[#{@name}]", :endkey => "role[#{@name}]", :include_docs => true) + rs["rows"].each do |row| + node = row["doc"] + node.run_list.remove("role[#{@name}]") + node.cdb_save end end - + # Remove this role via the REST API def destroy chef_server_rest.delete_rest("roles/#{@name}") - + Chef::Node.list.each do |node| n = Chef::Node.load(node[0]) n.run_list.remove("role[#{@name}]") n.save end - + end - + # Save this role to the CouchDB def cdb_save self.couchdb_rev = couchdb.store("role", @name, self)["rev"] end - + # Save this role via the REST API def save begin @@ -253,18 +245,18 @@ class Chef end self end - + # Create the role via the REST API def create chef_server_rest.post_rest("roles", self) self - end - + end + # Set up our CouchDB design document def self.create_design_document(couchdb=nil) (couchdb || Chef::CouchDB.new).create_design_document("roles", DESIGN_DOCUMENT) end - + # As a string def to_s "role[#{@name}]" @@ -289,14 +281,14 @@ class Chef # Sync all the json roles with couchdb from disk def self.sync_from_disk_to_couchdb Dir[File.join(Chef::Config[:role_path], "*.json")].each do |role_file| - short_name = File.basename(role_file, ".json") + short_name = File.basename(role_file, ".json") Chef::Log.warn("Loading #{short_name}") r = Chef::Role.from_disk(short_name, "json") begin couch_role = Chef::Role.cdb_load(short_name) r.couchdb_rev = couch_role.couchdb_rev Chef::Log.debug("Replacing role #{short_name} with data from #{role_file}") - rescue Chef::Exceptions::CouchDBNotFound + rescue Chef::Exceptions::CouchDBNotFound Chef::Log.debug("Creating role #{short_name} with data from #{role_file}") end r.cdb_save diff --git a/chef/lib/chef/streaming_cookbook_uploader.rb b/chef/lib/chef/streaming_cookbook_uploader.rb index f7e8b23ff9..1e1bb8f4dc 100644 --- a/chef/lib/chef/streaming_cookbook_uploader.rb +++ b/chef/lib/chef/streaming_cookbook_uploader.rb @@ -1,9 +1,15 @@ +# inspired by/cargo-culted from http://stanislavvitvitskiy.blogspot.com/2008/12/multipart-post-in-ruby.html +# On Apr 6, 2010, at 3:00 PM, Stanislav Vitvitskiy wrote: +# +# It's free to use / modify / distribute. No need to mention anything. Just copy/paste and use. +# +# Regards, +# Stan + require 'net/http' require 'mixlib/authentication/signedheaderauth' require 'openssl' -# inspired by/cargo-culted from http://stanislavvitvitskiy.blogspot.com/2008/12/multipart-post-in-ruby.html -# TODO: confirm that code is public domain class Chef class StreamingCookbookUploader diff --git a/chef/lib/chef/tasks/chef_repo.rake b/chef/lib/chef/tasks/chef_repo.rake index 58b1319d33..b22d84b25f 100644 --- a/chef/lib/chef/tasks/chef_repo.rake +++ b/chef/lib/chef/tasks/chef_repo.rake @@ -72,8 +72,8 @@ task :install => [ :update, :roles, :upload_cookbooks ] do end end -desc "By default, run rake test" -task :default => [ :test ] +desc "By default, run rake test_cookbooks" +task :default => [ :test_cookbooks ] desc "Create a new cookbook (with COOKBOOK=name, optional CB_PREFIX=site-)" task :new_cookbook do @@ -213,7 +213,7 @@ EOH end rule(%r{\b(?:site-)?cookbooks/[^/]+/metadata\.json\Z} => [ proc { |task_name| task_name.sub(/\.[^.]+$/, '.rb') } ]) do |t| - system("knife cookbook metadata #{t.source}") + system("knife cookbook metadata from file #{t.source}") end desc "Build cookbook metadata.json from metadata.rb" @@ -228,7 +228,7 @@ task :roles => FileList[File.join(TOPDIR, 'roles', '**', '*.rb')].pathmap('%X.j desc "Update a specific role" task :role, :role_name do |t, args| - system("knife role from file #{args.cookbook}") + system("knife role from file #{File.join(TOPDIR, 'roles', args.role_name)}.rb") end desc "Upload all cookbooks" @@ -243,3 +243,13 @@ task :upload_cookbook, :cookbook do |t, args| system("knife cookbook upload #{args.cookbook}") end +desc "Test all cookbooks" +task :test_cookbooks do + system("knife cookbook test --all") +end + +desc "Test a single cookbook" +task :test_cookbook, :cookbook do |t, args| + system("knife cookbook test #{args.cookbook}") +end + diff --git a/chef/lib/chef/util/file_edit.rb b/chef/lib/chef/util/file_edit.rb index 2ecaa98642..83b1568f08 100644 --- a/chef/lib/chef/util/file_edit.rb +++ b/chef/lib/chef/util/file_edit.rb @@ -15,7 +15,6 @@ # See the License for the specific language governing permissions and # limitations under the License. -require 'ftools' require 'fileutils' require 'tempfile' diff --git a/chef/lib/chef/webui_user.rb b/chef/lib/chef/webui_user.rb index 240e119dc5..fbfe1724d1 100644 --- a/chef/lib/chef/webui_user.rb +++ b/chef/lib/chef/webui_user.rb @@ -22,7 +22,6 @@ require 'chef/mixin/params_validate' require 'chef/couchdb' require 'chef/index_queue' require 'digest/sha1' -require 'rubygems' require 'json' diff --git a/chef/spec/data/metadata/quick_start/metadata.rb b/chef/spec/data/metadata/quick_start/metadata.rb new file mode 100644 index 0000000000..e74eedba0f --- /dev/null +++ b/chef/spec/data/metadata/quick_start/metadata.rb @@ -0,0 +1,19 @@ +maintainer "Opscode, Inc." +maintainer_email "cookbooks@opscode.com" +license "Apache 2.0" +description "Example cookbook for quick_start wiki document" +version "0.7" + +%w{ + redhat fedora centos + ubuntu debian + macosx freebsd openbsd + solaris +}.each do |os| + supports os +end + +attribute "quick_start/deep_thought", + :display_name => "Quick Start Deep Thought", + :description => "A deep thought", + :default => "If a tree falls in the forest..." diff --git a/chef/spec/data/ssl/chef-rspec.cert b/chef/spec/data/ssl/chef-rspec.cert new file mode 100644 index 0000000000..08ec684520 --- /dev/null +++ b/chef/spec/data/ssl/chef-rspec.cert @@ -0,0 +1,27 @@ +-----BEGIN CERTIFICATE----- +MIIEkjCCA3qgAwIBAgIJAKBJr4wSRUVvMA0GCSqGSIb3DQEBBQUAMIGMMQswCQYD +VQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHU2VhdHRsZTEN +MAsGA1UEChMEQ2hlZjETMBEGA1UECxMKZGV2ZWxvcGVyczESMBAGA1UEAxMJa2Fs +bGlzdGVjMR4wHAYJKoZIhvcNAQkBFg9kYW5Ab3BzY29kZS5jb20wHhcNMTAwNDEw +MTkxMTMxWhcNMjAwNDA3MTkxMTMxWjCBjDELMAkGA1UEBhMCVVMxEzARBgNVBAgT +Cldhc2hpbmd0b24xEDAOBgNVBAcTB1NlYXR0bGUxDTALBgNVBAoTBENoZWYxEzAR +BgNVBAsTCmRldmVsb3BlcnMxEjAQBgNVBAMTCWthbGxpc3RlYzEeMBwGCSqGSIb3 +DQEJARYPZGFuQG9wc2NvZGUuY29tMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEAw5l9EtBHsJrb5AIxARP695an3v+509gOXRKnjWIRnU+knbdTnEdjlGGG +SxuFR7Fnp2OM8ed7iPIKSrcM0vQ+g7vYKCv5Z8UR3sbLY8UHm9AgZ/bLAHEHS2if +1WHPD5DOe1B7HwW0IfEiW4/WakkVn4uoWw5rCZ87f4YCrETomXIo1n/rMFHf+yoY +guuEfGQxRcQdlEZM9YMlMByQvXlVR5IVhpiMHBCyV6KzxjZVCgTlvS8nPMiiHpoO +pgB6BGEQ/nn4Kapk40baPqpT4EP/DnBnbhhR3kBQ6MQRlh7bl5vjH5xFSFwGUUA9 +IcaDTwfliD27bo36aMvcBhrsmbSwqwIDAQABo4H0MIHxMB0GA1UdDgQWBBS88Zxt +vG+FTu1U+VFA47ffzwStbjCBwQYDVR0jBIG5MIG2gBS88ZxtvG+FTu1U+VFA47ff +zwStbqGBkqSBjzCBjDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24x +EDAOBgNVBAcTB1NlYXR0bGUxDTALBgNVBAoTBENoZWYxEzARBgNVBAsTCmRldmVs +b3BlcnMxEjAQBgNVBAMTCWthbGxpc3RlYzEeMBwGCSqGSIb3DQEJARYPZGFuQG9w +c2NvZGUuY29tggkAoEmvjBJFRW8wDAYDVR0TBAUwAwEB/zANBgkqhkiG9w0BAQUF +AAOCAQEAwwMrbuJAhP5uawJi5OYEaJKSbJGyahCcOAl4+ONgsdDoCy/9AZKzuFNc +C8vM/Ee6jyugrKMdckvZ883kJ4770HU6nbomCUVKMHMzJBE1Guvsn8wZP3nKyeSZ +eXXbH1b/NfstNyo6XLucaBRQvyvQYDUnk6osrBh+Gekvqsahr0wkVa8VUY2UySyY +60lYt4O92XJ1jWtYoFjRxeeUgo5E0TfIWj74kXhdMqwMf4Iv9VatfYR87ps5VMdf +Hp+nrCRaquDAs87LdO9e7M8l+W1ryPfP2inuGjIozsN5lLmwBdT+O6NkpmuxGPEG +ArIbYatR7+4MsDn+CjfkYblnmGLuug== +-----END CERTIFICATE----- diff --git a/chef/spec/data/ssl/chef-rspec.key b/chef/spec/data/ssl/chef-rspec.key new file mode 100644 index 0000000000..29aaecf2a6 --- /dev/null +++ b/chef/spec/data/ssl/chef-rspec.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAw5l9EtBHsJrb5AIxARP695an3v+509gOXRKnjWIRnU+knbdT +nEdjlGGGSxuFR7Fnp2OM8ed7iPIKSrcM0vQ+g7vYKCv5Z8UR3sbLY8UHm9AgZ/bL +AHEHS2if1WHPD5DOe1B7HwW0IfEiW4/WakkVn4uoWw5rCZ87f4YCrETomXIo1n/r +MFHf+yoYguuEfGQxRcQdlEZM9YMlMByQvXlVR5IVhpiMHBCyV6KzxjZVCgTlvS8n +PMiiHpoOpgB6BGEQ/nn4Kapk40baPqpT4EP/DnBnbhhR3kBQ6MQRlh7bl5vjH5xF +SFwGUUA9IcaDTwfliD27bo36aMvcBhrsmbSwqwIDAQABAoIBAQC+hddKaA4se+sL +4QaSoj+mwtypXjZHnv/+sJj8IjY+IMGbzmJmqzLX6VbB+gCMoMTySwmS54NxFTHp +LPwUz0vFTUdzecHpzg9mDAU5HUYYA1ZNbhq2R2JvlW16j1b9NnOpse77fLbFCPgK +b8TOqnmheot2hkjEipGN2Z7o5gYaz1/3PtolkP1ypCTG6Bh7V3ohBLBIEdjA552o +HNGe3t6PpvoNtBqaeb/j/SAOvg+8DGF1WQtE+5Y1koSlhABYWkHzHC1fHAzRMSHH +ZMfKOQNusRgBRNJabdVqkuTbvyRCQEb2YGQxPPYV2C+AxAlh3APeYTg90vUqAq/3 +ivNdilcBAoGBAOLELc0mcTftDbIMWVnrzAGAJOCMz3FkwGcV8nqNeA3R77e3pWL2 +5+bKadWQGjjpR3ZEYt/RxHsoGCW3NtM44icxqVCTPW/unp2xqadjuvcsKrxk+1wD +OdvVrwcd/N+KzgXO+Hm7xbV/loFms3ueGfCRbOueQyP4dj9MyOBGlO2hAoGBANzQ +u8IrZBG0DL8YFdmjw4YWUENIOtABPU1qHo/sugTQjI9K3/E3LA7aaGnl2P//1tao +SR/aP/To90H6D989/JomhkEKKA+DyL1sRL1NMdtWwrKdEq32W8fUN0JEA+Q1FMsd +Hk6Ix+KrZVg9cTb9HoGikDxeHW3pPKDWaEkWIQLLAoGAD13N4L3/JBQLPop5r487 +9soRNao1EHEMXK/vC4D0prMYNHHcYjVrB4el3lPygvLD5e7CaHpVfyb7Y+rjazLK +mG9UEuK3YhNgaj00yuQGMmOqzbNmGRka3ZvATZIppZhJV7lruwwPXLo1n7Uu6myP +Q28HW3wQ/qoCkU2JuzDtPKECgYBUrYcTEuixEUbCEU5vw6k7RltJMe27zn3frg5C +Sxmatw7v9Fqkee/fUkowMgBhS47rimVgXaWhGaWYG3jytyajRpq9XlO2f2b/nQFP +RscTwdWwASQkqhDQNMVsGAEWBnUO3v+8Rh/BANFAYW+FEtQcCmcdf0nx2DtzwkUD +ogTOuQKBgCbEg+/ND/p8xKwY9LtjLKnrQSL5tSH/7prhLJvVVdW7FMRfKSp1t2xc +kfJFqO1Lcf2j7hiclval3xDoWUretNQ5379T0Ob30WuIomSfeqcxJjCUtyN3fUqr +z/QG9dk/23OOYJhRgAmttBDqpk5uB5mOQgSftdELNyw0EOyNIBfZ +-----END RSA PRIVATE KEY----- diff --git a/chef/spec/data/ssl/private_key.pem b/chef/spec/data/ssl/private_key.pem new file mode 100644 index 0000000000..b6636604a8 --- /dev/null +++ b/chef/spec/data/ssl/private_key.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA49TA0y81ps0zxkOpmf5V4/c4IeR5yVyQFpX3JpxO4TquwnRh +8VSUhrw8kkTLmB3cS39Db+3HadvhoqCEbqPE6915kXSuk/cWIcNozujLK7tkuPEy +YVsyTioQAddSdfe+8EhQVf3oHxaKmUd6waXrWqYCnhxgOjxocenREYNhZ/OETIei +PbOku47vB4nJK/0GhKBytL2XnsRgfKgDxf42BqAi1jglIdeq8lAWZNF9TbNBU21A +O1iuT7Pm6LyQujhggPznR5FJhXKRUARXBJZawxpGV4dGtdcahwXNE4601aXPra+x +PcRd2puCNoEDBzgVuTSsLYeKBDMSfs173W1QYwIDAQABAoIBAGF05q7vqOGbMaSD +2Q7YbuE/JTHKTBZIlBI1QC2x+0P5GDxyEFttNMOVzcs7xmNhkpRw8eX1LrInrpMk +WsIBKAFFEfWYlf0RWtRChJjNl+szE9jQxB5FJnWtJH/FHa78tR6PsF24aQyzVcJP +g0FGujBihwgfV0JSCNOBkz8MliQihjQA2i8PGGmo4R4RVzGfxYKTIq9vvRq/+QEa +Q4lpVLoBqnENpnY/9PTl6JMMjW2b0spbLjOPVwDaIzXJ0dChjNXo15K5SHI5mALJ +I5gN7ODGb8PKUf4619ez194FXq+eob5YJdilTFKensIUvt3YhP1ilGMM+Chi5Vi/ +/RCTw3ECgYEA9jTw4wv9pCswZ9wbzTaBj9yZS3YXspGg26y6Ohq3ZmvHz4jlT6uR +xK+DDcUiK4072gci8S4Np0fIVS7q6ivqcOdzXPrTF5/j+MufS32UrBbUTPiM1yoO +ECcy+1szl/KoLEV09bghPbvC58PFSXV71evkaTETYnA/F6RK12lEepcCgYEA7OSy +bsMrGDVU/MKJtwqyGP9ubA53BorM4Pp9VVVSCrGGVhb9G/XNsjO5wJC8J30QAo4A +s59ZzCpyNRy046AB8jwRQuSwEQbejSdeNgQGXhZ7aIVUtuDeFFdaIz/zjVgxsfj4 +DPOuzieMmJ2MLR4F71ocboxNoDI7xruPSE8dDhUCgYA3vx732cQxgtHwAkeNPJUz +dLiE/JU7CnxIoSB9fYUfPLI+THnXgzp7NV5QJN2qzMzLfigsQcg3oyo6F2h7Yzwv +GkjlualIRRzCPaCw4Btkp7qkPvbs1QngIHALt8fD1N69P3DPHkTwjG4COjKWgnJq +qoHKS6Fe/ZlbigikI6KsuwKBgQCTlSLoyGRHr6oj0hqz01EDK9ciMJzMkZp0Kvn8 +OKxlBxYW+jlzut4MQBdgNYtS2qInxUoAnaz2+hauqhSzntK3k955GznpUatCqx0R +b857vWviwPX2/P6+E3GPdl8IVsKXCvGWOBZWTuNTjQtwbDzsUepWoMgXnlQJSn5I +YSlLxQKBgQD16Gw9kajpKlzsPa6XoQeGmZALT6aKWJQlrKtUQIrsIWM0Z6eFtX12 +2jjHZ0awuCQ4ldqwl8IfRogWMBkHOXjTPVK0YKWWlxMpD/5+bGPARa5fir8O1Zpo +Y6S6MeZ69Rp89ma4ttMZ+kwi1+XyHqC/dlcVRW42Zl5Dc7BALRlJjQ== +-----END RSA PRIVATE KEY----- diff --git a/chef/spec/spec_helper.rb b/chef/spec/spec_helper.rb index 9d2e2c3d91..c98a4f51bd 100644 --- a/chef/spec/spec_helper.rb +++ b/chef/spec/spec_helper.rb @@ -22,23 +22,21 @@ module Shef IRB = nil unless defined? IRB end +require 'rubygems' + $:.unshift(File.join(File.dirname(__FILE__), "..", "lib")) -$:.unshift(File.join(File.dirname(__FILE__), "..", "..", "chef-server", "lib")) require 'chef' -require File.join(File.dirname(__FILE__), "/../lib/chef/util/file_edit") +require 'chef/knife' +Chef::Knife.load_commands +require 'chef/mixins' +require 'chef/application' +require 'chef/applications' + +require 'chef/shef' +require 'chef/util/file_edit' + -chef_lib_path = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib')) -Dir[ - File.expand_path( - File.join( - chef_lib_path, 'chef', '**', '*.rb' - ) - ) -].sort.each do |lib| - lib_short_path = lib.match("^#{chef_lib_path}#{File::SEPARATOR}(.+)$")[1] - require lib_short_path -end Dir[File.join(File.dirname(__FILE__), 'lib', '**', '*.rb')].sort.each { |lib| require lib } Chef::Config[:log_level] = :fatal @@ -50,6 +48,8 @@ Chef::Config.solo(false) Chef::Log.logger = Logger.new(StringIO.new) +CHEF_SPEC_DATA = File.expand_path(File.dirname(__FILE__) + "/data/") + def redefine_argv(value) Object.send(:remove_const, :ARGV) Object.send(:const_set, :ARGV, value) diff --git a/chef/spec/unit/application/client_spec.rb b/chef/spec/unit/application/client_spec.rb index 509c6f3001..c9e229fcf2 100644 --- a/chef/spec/unit/application/client_spec.rb +++ b/chef/spec/unit/application/client_spec.rb @@ -143,12 +143,6 @@ describe Chef::Application::Client, "setup_application" do @app.setup_application end - it "should assign the validation token to the chef client instance" do - Chef::Config.stub!(:[]).with(:validation_token).and_return("testtoken") - @chef_client.should_receive(:validation_token=).with("testtoken").and_return(true) - @app.setup_application - end - it "should assign the node name to the chef client instance" do Chef::Config.stub!(:[]).with(:node_name).and_return("testnode") @chef_client.should_receive(:node_name=).with("testnode").and_return(true) diff --git a/chef/spec/unit/application_spec.rb b/chef/spec/unit/application_spec.rb index 917e8b4c31..90d7ee3bb1 100644 --- a/chef/spec/unit/application_spec.rb +++ b/chef/spec/unit/application_spec.rb @@ -20,11 +20,17 @@ require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper")) describe Chef::Application, "initialize" do before do @app = Chef::Application.new + Dir.stub!(:chdir).and_return(0) end it "should create an instance of Chef::Application" do @app.should be_kind_of(Chef::Application) end + + it "should chdir to root" do + Dir.should_receive(:chdir).with("/").and_return(0) + Chef::Application.new + end end diff --git a/chef/spec/unit/cache/checksum_spec.rb b/chef/spec/unit/cache/checksum_spec.rb index 22f37e1b4a..7544b923f0 100644 --- a/chef/spec/unit/cache/checksum_spec.rb +++ b/chef/spec/unit/cache/checksum_spec.rb @@ -52,7 +52,7 @@ describe Chef::Cache::Checksum do end it "computes a checksum of a file" do - fixture_file = File.dirname(__FILE__) + "/../../data/checksum/random.txt" + fixture_file = CHEF_SPEC_DATA + "/checksum/random.txt" expected = "09ee9c8cc70501763563bcf9c218d71b2fbf4186bf8e1e0da07f0f42c80a3394" @cache.send(:checksum_file, fixture_file).should == expected end @@ -65,9 +65,27 @@ describe Chef::Cache::Checksum do end it "returns a generated checksum if there is no cached value" do - fixture_file = File.dirname(__FILE__) + "/../../data/checksum/random.txt" + fixture_file = CHEF_SPEC_DATA + "/checksum/random.txt" expected = "09ee9c8cc70501763563bcf9c218d71b2fbf4186bf8e1e0da07f0f42c80a3394" @cache.checksum_for_file(fixture_file).should == expected end + it "generates a key from a file name" do + file = "/this/is/a/test/random.rb" + @cache.generate_key(file).should == "chef-file--this-is-a-test-random-rb" + end + + it "generates a key from a file name and group" do + file = "/this/is/a/test/random.rb" + @cache.generate_key(file, "spec").should == "spec-file--this-is-a-test-random-rb" + end + + it "returns a cached checksum value using a user defined key" do + key = @cache.generate_key("riseofthemachines", "specs") + @cache.moneta[key] = {"mtime" => "12345", "checksum" => "123abc"} + fstat = mock("File.stat('riseofthemachines')", :mtime => Time.at(12345)) + File.should_receive(:stat).with("riseofthemachines").and_return(fstat) + @cache.checksum_for_file("riseofthemachines", key).should == "123abc" + end + end diff --git a/chef/spec/unit/client_spec.rb b/chef/spec/unit/client_spec.rb index 8e8c366d6e..b3b55bc49a 100644 --- a/chef/spec/unit/client_spec.rb +++ b/chef/spec/unit/client_spec.rb @@ -77,7 +77,7 @@ describe Chef::Client, "run" do end it "should save the nodes state on the server (twice!)" do - @client.should_receive(:save_node).exactly(3).times.and_return(true) + @client.should_receive(:save_node).exactly(2).times.and_return(true) @client.run end @@ -114,6 +114,7 @@ describe Chef::Client, "run_solo" do it "should start/stop the run timer" do time = Time.now Time.should_receive(:now).at_least(1).times.and_return(time) + Chef::Config[:cookbook_path] = [File.join(CHEF_SPEC_DATA, "kitchen"), File.join(CHEF_SPEC_DATA, "cookbooks")] @client.run_solo end @@ -128,9 +129,9 @@ describe Chef::Client, "run_solo" do end it "should use the configured cookbook_path" do - Chef::Config[:cookbook_path] = ['one', 'two'] + Chef::Config[:cookbook_path] = [File.join(CHEF_SPEC_DATA, "kitchen"), File.join(CHEF_SPEC_DATA, "cookbooks")] @client.run_solo - Chef::Config[:cookbook_path].should eql(['one', 'two']) + Chef::Config[:cookbook_path].should eql([File.join(CHEF_SPEC_DATA, "kitchen"), File.join(CHEF_SPEC_DATA, "cookbooks")]) end it "should run report handlers" do diff --git a/chef/spec/unit/compile_spec.rb b/chef/spec/unit/compile_spec.rb index f8b2f7c5b3..5a6d2d51dd 100644 --- a/chef/spec/unit/compile_spec.rb +++ b/chef/spec/unit/compile_spec.rb @@ -20,8 +20,8 @@ require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper")) describe Chef::Compile do before(:each) do - Chef::Config.node_path(File.join(File.dirname(__FILE__), "..", "data", "compile", "nodes")) - Chef::Config.cookbook_path(File.join(File.dirname(__FILE__), "..", "data", "compile", "cookbooks")) + Chef::Config.node_path(File.expand_path(File.join(CHEF_SPEC_DATA, "compile", "nodes"))) + Chef::Config.cookbook_path(File.expand_path(File.join(CHEF_SPEC_DATA, "compile", "cookbooks"))) @node = Chef::Node.new @compile = Chef::Compile.new(@node) @compile.go diff --git a/chef/spec/unit/cookbook_loader_spec.rb b/chef/spec/unit/cookbook_loader_spec.rb index 1c5a89785b..f736f66eea 100644 --- a/chef/spec/unit/cookbook_loader_spec.rb +++ b/chef/spec/unit/cookbook_loader_spec.rb @@ -21,8 +21,8 @@ require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper")) describe Chef::CookbookLoader do before(:each) do Chef::Config.cookbook_path [ - File.join(File.dirname(__FILE__), "..", "data", "kitchen"), - File.join(File.dirname(__FILE__), "..", "data", "cookbooks") + File.expand_path(File.join(CHEF_SPEC_DATA, "kitchen")), + File.expand_path(File.join(CHEF_SPEC_DATA, "cookbooks")) ] @cl = Chef::CookbookLoader.new() end @@ -74,7 +74,7 @@ describe Chef::CookbookLoader do describe "load_cookbooks" do it "should find all the cookbooks in the cookbook path" do - Chef::Config.cookbook_path << File.join(File.dirname(__FILE__), "..", "data", "hidden-cookbooks") + Chef::Config.cookbook_path << File.expand_path(File.join(CHEF_SPEC_DATA, "hidden-cookbooks")) @cl.load_cookbooks @cl.detect { |cb| cb.name == :openldap }.should_not eql(nil) @cl.detect { |cb| cb.name == :apache2 }.should_not eql(nil) diff --git a/chef/spec/unit/cookbook_spec.rb b/chef/spec/unit/cookbook_spec.rb index 9f9d4a24c1..8f2ea13abe 100644 --- a/chef/spec/unit/cookbook_spec.rb +++ b/chef/spec/unit/cookbook_spec.rb @@ -19,7 +19,7 @@ require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper")) describe Chef::Cookbook do - COOKBOOK_PATH = File.join(File.dirname(__FILE__), "..", "data", "cookbooks", "openldap") + COOKBOOK_PATH = File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "cookbooks", "openldap")) before(:each) do @cookbook = Chef::Cookbook.new("openldap") diff --git a/chef/spec/unit/daemon_spec.rb b/chef/spec/unit/daemon_spec.rb index 99bc97d681..b90f9184bf 100644 --- a/chef/spec/unit/daemon_spec.rb +++ b/chef/spec/unit/daemon_spec.rb @@ -111,7 +111,7 @@ describe Chef::Daemon do end it "should write the pid, converted to string, to the pid file" do - @f_mock.should_receive(:write, "1337").once.and_return(true) + @f_mock.should_receive(:write).with("1337").once.and_return(true) Chef::Daemon.save_pid_file end @@ -164,7 +164,7 @@ describe Chef::Daemon do end it "should log an appropriate info message" do - Chef::Log.should_receive(:info, "About to change privilege to aj:staff") + Chef::Log.should_receive(:info).with("About to change privilege to aj:staff") Chef::Daemon.change_privilege end @@ -180,7 +180,7 @@ describe Chef::Daemon do end it "should log an appropriate info message" do - Chef::Log.should_receive(:info, "About to change privilege to aj") + Chef::Log.should_receive(:info).with("About to change privilege to aj") Chef::Daemon.change_privilege end diff --git a/chef/spec/unit/file_cache_spec.rb b/chef/spec/unit/file_cache_spec.rb index ad87bc2fe6..7026a4368b 100644 --- a/chef/spec/unit/file_cache_spec.rb +++ b/chef/spec/unit/file_cache_spec.rb @@ -6,9 +6,9 @@ # 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. @@ -26,7 +26,7 @@ describe Chef::FileCache, "store method" do @io = mock("IO", { :print => true, :close => true }) File.stub!(:open).and_return(@io) end - + it "should create the directories leading up to bang" do File.stub!(:directory?).and_return(false) Dir.should_receive(:mkdir).with("/tmp").and_return(true) @@ -35,17 +35,17 @@ describe Chef::FileCache, "store method" do Dir.should_not_receive(:mkdir).with("/tmp/foo/whiz/bang").and_return(true) Chef::FileCache.store("whiz/bang", "I found a poop") end - + it "should create a file at /tmp/foo/whiz/bang" do File.should_receive(:open).with("/tmp/foo/whiz/bang", "w").and_return(@io) Chef::FileCache.store("whiz/bang", "I found a poop") end - + it "should print the contents to the file" do @io.should_receive(:print).with("I found a poop") Chef::FileCache.store("whiz/bang", "I found a poop") end - + it "should close the file" do @io.should_receive(:close) Chef::FileCache.store("whiz/bang", "I found a poop") @@ -61,18 +61,18 @@ describe Chef::FileCache, "load method" do File.stub!(:exists?).and_return(true) File.stub!(:read).and_return("I found a poop") end - + it "should find the full path to whiz/bang" do File.should_receive(:read).with("/tmp/foo/whiz/bang").and_return(true) Chef::FileCache.load('whiz/bang') end - + it "should raise a Chef::Exceptions::FileNotFound if the file doesn't exist" do File.stub!(:exists?).and_return(false) lambda { Chef::FileCache.load('whiz/bang') }.should raise_error(Chef::Exceptions::FileNotFound) end end - + describe Chef::FileCache, "delete method" do before(:each) do Chef::Config[:file_cache_path] = "/tmp/foo" @@ -81,21 +81,22 @@ describe Chef::FileCache, "delete method" do File.stub!(:exists?).and_return(true) File.stub!(:unlink).and_return(true) end - + it "should unlink the full path to whiz/bang" do File.should_receive(:unlink).with("/tmp/foo/whiz/bang").and_return(true) Chef::FileCache.delete("whiz/bang") end - + end describe Chef::FileCache, "list method" do before(:each) do Chef::Config[:file_cache_path] = "/tmp/foo" - Dir.stub!(:[]).and_return(["/tmp/foo/whiz/bang", "/tmp/foo/snappy/patter"]) + Dir.stub!(:[]).with(File.join(Chef::Config[:file_cache_path], '**', '*')).and_return(["/tmp/foo/whiz/bang", "/tmp/foo/snappy/patter"]) + Dir.stub!(:[]).with(Chef::Config[:file_cache_path]).and_return(["/tmp/foo"]) File.stub!(:file?).and_return(true) end - + it "should return the relative paths" do Chef::FileCache.list.should eql([ "whiz/bang", "snappy/patter" ]) end @@ -105,17 +106,17 @@ describe Chef::FileCache, "has_key? method" do before(:each) do Chef::Config[:file_cache_path] = "/tmp/foo" end - + it "should check the full path to the file" do File.should_receive(:exists?).with("/tmp/foo/whiz/bang") Chef::FileCache.has_key?("whiz/bang") end - + it "should return true if the file exists" do File.stub!(:exists?).and_return(true) Chef::FileCache.has_key?("whiz/bang").should eql(true) end - + it "should return false if the file does not exist" do File.stub!(:exists?).and_return(false) Chef::FileCache.has_key?("whiz/bang").should eql(false) diff --git a/chef/spec/unit/knife/client_bulk_delete_spec.rb b/chef/spec/unit/knife/client_bulk_delete_spec.rb index a0c9b04e37..0152c4cb34 100644 --- a/chef/spec/unit/knife/client_bulk_delete_spec.rb +++ b/chef/spec/unit/knife/client_bulk_delete_spec.rb @@ -25,7 +25,7 @@ describe Chef::Knife::ClientBulkDelete do :print_after => nil } @knife.name_args = ["."] - @knife.stub!(:json_pretty_print).and_return(:true) + @knife.stub!(:output).and_return(:true) @knife.stub!(:confirm).and_return(true) @clients = Hash.new %w{tim dan stephen}.each do |client_name| @@ -45,7 +45,7 @@ describe Chef::Knife::ClientBulkDelete do end it "should print the clients you are about to delete" do - @knife.should_receive(:json_pretty_print).with(@knife.format_list_for_display(@clients)) + @knife.should_receive(:output).with(@knife.format_list_for_display(@clients)) @knife.run end @@ -78,7 +78,7 @@ describe Chef::Knife::ClientBulkDelete do it "should pretty_print the client, formatted for display" do @knife.config[:print_after] = true @clients.each_value do |n| - @knife.should_receive(:json_pretty_print).with(@knife.format_for_display(n)) + @knife.should_receive(:output).with(@knife.format_for_display(n)) end @knife.run end diff --git a/chef/spec/unit/knife/configure_spec.rb b/chef/spec/unit/knife/configure_spec.rb new file mode 100644 index 0000000000..381dc5f7b8 --- /dev/null +++ b/chef/spec/unit/knife/configure_spec.rb @@ -0,0 +1,88 @@ +require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper') + +describe Chef::Knife::Configure do + before do + @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.stub!(:stdout).and_return(@out) + @knife.config[:config_file] = '/home/you/.chef/knife.rb' + + @in = StringIO.new("\n" * 7) + @knife.stub!(:stdin).and_return(@in) + end + + it "asks the user for the URL of the chef server" do + @knife.ask_user_for_config + @out.string.should match(Regexp.escape 'Your chef server URL? [http://localhost:4000]') + @knife.chef_server.should == 'http://localhost:4000' + end + + it "asks the user for the user name they want for the new client" do + @knife.ask_user_for_config + @out.string.should match(Regexp.escape "Select a user name for your new client: [#{Etc.getlogin}]") + @knife.new_client_name.should == Etc.getlogin + end + + it "asks the user for the existing admin client's name" do + @knife.ask_user_for_config + @out.string.should match(Regexp.escape "Your existing admin client user name? [chef-webui]") + @knife.admin_client_name.should == 'chef-webui' + end + + it "asks the user for the location of the existing admin key" do + @knife.ask_user_for_config + @out.string.should match(Regexp.escape "The location of your existing admin key? [/etc/chef/webui.pem]") + @knife.admin_client_key.should == '/etc/chef/webui.pem' + end + + it "asks the user for the location of a chef repo" do + @knife.ask_user_for_config + @out.string.should match(Regexp.escape "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 "Your validation client user name? [chef-validator]") + @knife.validation_client_name.should == 'chef-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 "The location of your validation key? [/etc/chef/validation.pem]") + @knife.validation_key.should == '/etc/chef/validation.pem' + end + + it "writes the new data to a config file" do + 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://localhost:4000'$} + config_file.string.should match %r{cookbook_path\s+\[ '/home/you/chef-repo/cookbooks', '/home/you/chef-repo/site-cookbooks' \]} + end + + it "creates a new client when given the --initial option" do + client_command = Chef::Knife::ClientCreate.new + client_command.should_receive(:run) + + 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(Etc.getlogin) + client_command.config[:admin].should be_true + client_command.config[:file].should == "/home/you/.chef/#{Etc.getlogin}.pem" + client_command.config[:yes].should be_true + client_command.config[:no_editor].should be_true + end +end
\ No newline at end of file diff --git a/chef/spec/unit/knife/cookbook_bulk_delete_spec.rb b/chef/spec/unit/knife/cookbook_bulk_delete_spec.rb index a0e3abd696..f14d4b650f 100644 --- a/chef/spec/unit/knife/cookbook_bulk_delete_spec.rb +++ b/chef/spec/unit/knife/cookbook_bulk_delete_spec.rb @@ -25,7 +25,7 @@ describe Chef::Knife::CookbookBulkDelete do :print_after => nil } @knife.name_args = ["."] - @knife.stub!(:json_pretty_print).and_return(:true) + @knife.stub!(:output).and_return(:true) @knife.stub!(:confirm).and_return(true) @cookbooks = Hash.new %w{cheezburger pizza lasagna}.each do |cookbook_name| @@ -46,7 +46,7 @@ describe Chef::Knife::CookbookBulkDelete do end it "should print the cookbooks you are about to delete" do - @knife.should_receive(:json_pretty_print).with(@knife.format_list_for_display(@cookbooks)) + @knife.should_receive(:output).with(@knife.format_list_for_display(@cookbooks)) @knife.run end @@ -79,7 +79,7 @@ describe Chef::Knife::CookbookBulkDelete do it "should pretty_print the node, formatted for display" do @knife.config[:print_after] = true @cookbooks.each_value do |n| - @knife.should_receive(:json_pretty_print).with(@knife.format_for_display(n)) + @knife.should_receive(:output).with(@knife.format_for_display(n)) end @knife.run end diff --git a/chef/spec/unit/knife/cookbook_metadata_from_file_spec.rb b/chef/spec/unit/knife/cookbook_metadata_from_file_spec.rb new file mode 100644 index 0000000000..34d3e5c187 --- /dev/null +++ b/chef/spec/unit/knife/cookbook_metadata_from_file_spec.rb @@ -0,0 +1,64 @@ +# +# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper")) + +describe Chef::Knife::CookbookMetadataFromFile do + before(:each) do + @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!(:json_pretty_generate).and_return(true) + @md = Chef::Cookbook::Metadata.new + Chef::Cookbook::Metadata.stub(:new).and_return(@md) + 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 + JSON.should_receive(:pretty_generate).with(@md) + @knife.run + end + + end +end + diff --git a/chef/spec/unit/knife/cookbook_test_spec.rb b/chef/spec/unit/knife/cookbook_test_spec.rb new file mode 100644 index 0000000000..b9f65af55a --- /dev/null +++ b/chef/spec/unit/knife/cookbook_test_spec.rb @@ -0,0 +1,178 @@ +# +# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper")) + +describe Chef::Knife::CookbookTest do + before(:each) do + @knife = Chef::Knife::CookbookTest.new + @cookbooks = [] + %w{tats central_market jimmy_johns pho}.each do |cookbook_name| + @cookbooks << Chef::Cookbook.new(cookbook_name) + end + 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.stub!(:test_ruby).and_return(true) + @knife.stub!(:test_template).and_return(true) + @knife.name_args = ["example"] + Array(Chef::Config[:cookbook_path]).reverse.each do |path| + @knife.should_receive(:test_ruby).with(File.join(path, "example")).ordered + @knife.should_receive(:test_templates).with(File.expand_path(File.join(path, "example"))).ordered + end + @knife.run + end + + describe "syntax checks" do + before(:each) do + @path = [ File.expand_path(File.join(CHEF_SPEC_DATA, "cookbooks")) ] + @knife.config[:cookbook_path] = @path + @knife.name_args = ["openldap"] + + Chef::Mixin::Command.stub!(:run_command).and_return(true) + + @cache = Chef::Cache::Checksum.instance + @cache.reset!("Memory", {}) + Chef::Cache::Checksum.stub(:instance).and_return(@cache) + end + + it "should execute the ruby syntax check" do + @knife.stub!(:test_templates).and_return(true) + Dir[File.join(@path, 'openldap', '**', '*.rb')].each do |file| + Chef::Mixin::Command.should_receive(:run_command).with({:command =>"ruby -c #{file}", :output_on_failure=>true}) + end + @knife.run + end + + it "should execute the erb template syntax check" do + @knife.stub!(:test_ruby).and_return(true) + Dir[File.join(@path, 'openldap', '**', '*.erb')].each do |file| + Chef::Mixin::Command.should_receive(:run_command).with({:command =>"sh -c 'erubis -x #{file} | ruby -c'", :output_on_failure=>true}) + end + @knife.run + end + + it "should instantiate the cache for ruby syntax check" do + @knife.stub!(:test_templates).and_return(true) + Chef::Cache::Checksum.should_receive(:instance) + @knife.run + end + + it "should instantiate the cache for the erb template syntax check" do + @knife.stub!(:test_ruby).and_return(true) + Chef::Cache::Checksum.should_receive(:instance) + @knife.run + end + + it "should hit the cache and not execute the ruby syntax checks" do + @knife.stub!(:test_templates).and_return(true) + @cache.stub!(:lookup_checksum).and_return(true) + Chef::Mixin::Command.should_not_receive(:run_command) + @knife.run + end + + it "should miss when checking the cache and execute the ruby syntax checks" do + @knife.stub!(:test_templates).and_return(true) + @cache.stub!(:lookup_checksum).and_return(false) + Chef::Mixin::Command.should_receive(:run_command).at_least(:once) + @knife.run + end + + it "should hit the cache and not execute the erb template syntax checks" do + @knife.stub!(:test_ruby).and_return(true) + @cache.stub!(:lookup_checksum).and_return(true) + Chef::Mixin::Command.should_not_receive(:run_command) + @knife.run + end + + it "should miss when checking the cache and execute the erb template syntax checks" do + @knife.stub!(:test_ruby).and_return(true) + @cache.stub!(:lookup_checksum).and_return(false) + Chef::Mixin::Command.should_receive(:run_command).at_least(:once) + @knife.run + end + + it "should generate a checksum when the ruby syntax check was successful" do + @knife.stub!(:test_templates).and_return(true) + @cache.stub!(:lookup_checksum).and_return(false) + @cache.should_receive(:generate_checksum).at_least(:once) + @knife.run + end + + it "should not generate a checksum when the ruby syntax check fails" do + @knife.stub!(:test_templates).and_return(true) + @cache.stub!(:lookup_checksum).and_return(false) + Chef::Mixin::Command.stub!(:run_command).and_raise(Chef::Exceptions::Exec) + @cache.should_not_receive(:generate_checksum) + lambda { @knife.run }.should raise_error(Chef::Exceptions::Exec) + end + + it "should generate a checksum when the template syntax check was successful" do + @knife.stub!(:test_ruby).and_return(true) + @cache.stub!(:lookup_checksum).and_return(false) + @cache.should_receive(:generate_checksum).at_least(:once) + @knife.run + end + + it "should not generate a checksum when the template syntax check fails" do + @knife.stub!(:test_ruby).and_return(true) + @cache.stub!(:lookup_checksum).and_return(false) + Chef::Mixin::Command.stub!(:run_command).and_raise(Chef::Exceptions::Exec) + @cache.should_not_receive(:generate_checksum) + lambda { @knife.run }.should raise_error(Chef::Exceptions::Exec) + end + end + + describe "with -a or --all" do + it "should upload all of the cookbooks" do + @knife.stub!(:test_cookbook).and_return(true) + @knife.config[:all] = true + @loader = mock("Chef::CookbookLoader") + @cookbooks.inject(@loader.stub!(:each)) { |stub, cookbook| + stub.and_yield(cookbook) + } + Chef::CookbookLoader.stub!(:new).and_return(@loader) + @cookbooks.each do |cookbook| + @knife.should_receive(:test_cookbook).with(cookbook.name) + end + @knife.run + end + end + + end +end diff --git a/chef/spec/unit/knife/cookboook_show_spec.rb b/chef/spec/unit/knife/cookboook_show_spec.rb index f138a1f752..1c4653110a 100644 --- a/chef/spec/unit/knife/cookboook_show_spec.rb +++ b/chef/spec/unit/knife/cookboook_show_spec.rb @@ -26,7 +26,7 @@ describe Chef::Knife::CookbookShow do @rest = mock(Chef::REST, :null_object => true) @knife.stub!(:rest).and_return(@rest) @knife.stub!(:pretty_print).and_return(true) - @knife.stub!(:json_pretty_print).and_return(true) + @knife.stub!(:output).and_return(true) end describe "run" do @@ -34,7 +34,7 @@ describe Chef::Knife::CookbookShow do it "should show the raw cookbook data" do response = { "snootch" => "to the bootch" } @rest.should_receive(:get_rest).with("cookbooks/adam").and_return(response) - @knife.should_receive(:json_pretty_print).with(response) + @knife.should_receive(:output).with(response) @knife.run end end @@ -47,7 +47,7 @@ describe Chef::Knife::CookbookShow do it "should show the specific part of a cookbook" do @rest.should_receive(:get_rest).with("cookbooks/adam").and_return(@response) - @knife.should_receive(:json_pretty_print).with(@response["snootchy"]) + @knife.should_receive(:output).with(@response["snootchy"]) @knife.run end end diff --git a/chef/spec/unit/knife/index_rebuild_spec.rb b/chef/spec/unit/knife/index_rebuild_spec.rb index 93f3f5b72d..5e90200ccf 100644 --- a/chef/spec/unit/knife/index_rebuild_spec.rb +++ b/chef/spec/unit/knife/index_rebuild_spec.rb @@ -6,9 +6,9 @@ # 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. @@ -23,38 +23,41 @@ describe Chef::Knife::IndexRebuild do @knife = Chef::Knife::IndexRebuild.new @knife.stub!(:edit_data) @rest_client = mock("null rest client", :post_rest => { :result => :true }) - @knife.stub!(:json_pretty_print) + @knife.stub!(:output) @knife.stub!(:rest).and_return(@rest_client) + + @out = StringIO.new + @knife.stub!(:stdout).and_return(@out) end - + it "asks a yes/no confirmation and aborts on 'no'" do - @knife.should_receive(:print).with(/yes\/no/) - STDIN.stub(:readline).and_return("NO\n") + @knife.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.should_receive(:print).with(/yes\/no/) - STDIN.stub(:readline).and_return("yes\n") + @knife.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.stub!(:print) @knife.stub!(:puts) @knife.stub!(:nag) - @knife.stub!(:json_pretty_print) + @knife.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(:json_pretty_print).with("monkey") + @knife.should_receive(:output).with("monkey") @knife.run end end diff --git a/chef/spec/unit/knife/node_bulk_delete_spec.rb b/chef/spec/unit/knife/node_bulk_delete_spec.rb index ab99583728..04c3366ef5 100644 --- a/chef/spec/unit/knife/node_bulk_delete_spec.rb +++ b/chef/spec/unit/knife/node_bulk_delete_spec.rb @@ -25,7 +25,7 @@ describe Chef::Knife::NodeBulkDelete do :print_after => nil } @knife.name_args = ["."] - @knife.stub!(:json_pretty_print).and_return(true) + @knife.stub!(:output).and_return(true) @knife.stub!(:confirm).and_return(true) @nodes = Hash.new %w{adam brent jacob}.each do |node_name| @@ -45,7 +45,7 @@ describe Chef::Knife::NodeBulkDelete do 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.should_receive(:output).with(@knife.format_list_for_display(@nodes)) @knife.run end @@ -78,7 +78,7 @@ describe Chef::Knife::NodeBulkDelete 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)) + @knife.should_receive(:output).with(@knife.format_for_display(n)) end @knife.run end diff --git a/chef/spec/unit/knife/node_delete_spec.rb b/chef/spec/unit/knife/node_delete_spec.rb index a8a410cafa..2d877460f1 100644 --- a/chef/spec/unit/knife/node_delete_spec.rb +++ b/chef/spec/unit/knife/node_delete_spec.rb @@ -25,7 +25,7 @@ describe Chef::Knife::NodeDelete do :print_after => nil } @knife.name_args = [ "adam" ] - @knife.stub!(:json_pretty_print).and_return(true) + @knife.stub!(:output).and_return(true) @knife.stub!(:confirm).and_return(true) @node = Chef::Node.new() @node.stub!(:destroy).and_return(true) @@ -49,7 +49,7 @@ describe Chef::Knife::NodeDelete do end it "should not print the node" do - @knife.should_not_receive(:json_pretty_print).with("poop") + @knife.should_not_receive(:output).with("poop") @knife.run end @@ -57,7 +57,7 @@ describe Chef::Knife::NodeDelete 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.should_receive(:output).with("poop") @knife.run end end diff --git a/chef/spec/unit/knife/node_edit_spec.rb b/chef/spec/unit/knife/node_edit_spec.rb index 9a2f16a3d8..62fd69c9bd 100644 --- a/chef/spec/unit/knife/node_edit_spec.rb +++ b/chef/spec/unit/knife/node_edit_spec.rb @@ -26,7 +26,7 @@ describe Chef::Knife::NodeEdit do :print_after => nil } @knife.name_args = [ "adam" ] - @knife.stub!(:json_pretty_print).and_return(true) + @knife.stub!(:output).and_return(true) @node = Chef::Node.new() @node.stub!(:save) Chef::Node.stub!(:load).and_return(@node) @@ -52,7 +52,7 @@ describe Chef::Knife::NodeEdit do end it "should not print the node" do - @knife.should_not_receive(:json_pretty_print).with("poop") + @knife.should_not_receive(:output).with("poop") @knife.run end @@ -60,7 +60,7 @@ describe Chef::Knife::NodeEdit 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.should_receive(:output).with("poop") @knife.run end end diff --git a/chef/spec/unit/knife/node_from_file_spec.rb b/chef/spec/unit/knife/node_from_file_spec.rb index 2fb7f59ae7..5396e59952 100644 --- a/chef/spec/unit/knife/node_from_file_spec.rb +++ b/chef/spec/unit/knife/node_from_file_spec.rb @@ -25,7 +25,7 @@ describe Chef::Knife::NodeFromFile do :print_after => nil } @knife.name_args = [ "adam.rb" ] - @knife.stub!(:json_pretty_print).and_return(true) + @knife.stub!(:output).and_return(true) @knife.stub!(:confirm).and_return(true) @node = Chef::Node.new() @node.stub!(:save) @@ -39,14 +39,14 @@ describe Chef::Knife::NodeFromFile do end it "should not print the Node" do - @knife.should_not_receive(:json_pretty_print) + @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(:json_pretty_print) + @knife.should_receive(:output) @knife.run end end diff --git a/chef/spec/unit/knife/node_list_spec.rb b/chef/spec/unit/knife/node_list_spec.rb index 2fbcf765a4..f354082cb7 100644 --- a/chef/spec/unit/knife/node_list_spec.rb +++ b/chef/spec/unit/knife/node_list_spec.rb @@ -21,7 +21,7 @@ require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_hel describe Chef::Knife::NodeList do before(:each) do @knife = Chef::Knife::NodeList.new - @knife.stub!(:json_pretty_print).and_return(true) + @knife.stub!(:output).and_return(true) @list = { "foo" => "http://example.com/foo", "bar" => "http://example.com/foo" @@ -37,7 +37,7 @@ describe Chef::Knife::NodeList do 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.should_receive(:output).with([ "bar", "foo" ]) @knife.run end @@ -45,7 +45,7 @@ describe Chef::Knife::NodeList 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.should_receive(:output).with(@list) @knife.run 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 index bc3b1a8a65..818c7d22ca 100644 --- a/chef/spec/unit/knife/node_run_list_add_spec.rb +++ b/chef/spec/unit/knife/node_run_list_add_spec.rb @@ -25,7 +25,7 @@ describe Chef::Knife::NodeRunListAdd do :after => nil } @knife.name_args = [ "adam", "role[monkey]" ] - @knife.stub!(:json_pretty_print).and_return(true) + @knife.stub!(:output).and_return(true) @node = Chef::Node.new() @node.stub!(:save).and_return(true) Chef::Node.stub!(:load).and_return(@node) @@ -48,7 +48,7 @@ describe Chef::Knife::NodeRunListAdd do end it "should print the run list" do - @knife.should_receive(:json_pretty_print).and_return(true) + @knife.should_receive(:output).and_return(true) @knife.run end diff --git a/chef/spec/unit/knife/node_run_list_remove_spec.rb b/chef/spec/unit/knife/node_run_list_remove_spec.rb index fbf25225ad..5687d84f06 100644 --- a/chef/spec/unit/knife/node_run_list_remove_spec.rb +++ b/chef/spec/unit/knife/node_run_list_remove_spec.rb @@ -25,7 +25,7 @@ describe Chef::Knife::NodeRunListRemove do :print_after => nil } @knife.name_args = [ "adam", "role[monkey]" ] - @knife.stub!(:json_pretty_print).and_return(true) + @knife.stub!(:output).and_return(true) @knife.stub!(:confirm).and_return(true) @node = Chef::Node.new() @node.run_list << "role[monkey]" @@ -50,7 +50,7 @@ describe Chef::Knife::NodeRunListRemove do end it "should print the run list" do - @knife.should_receive(:json_pretty_print).with({ 'run_list' => [] }) + @knife.should_receive(:output).with({ 'run_list' => [] }) @knife.run end end diff --git a/chef/spec/unit/knife/node_show_spec.rb b/chef/spec/unit/knife/node_show_spec.rb index b711b66e2b..9e48702f43 100644 --- a/chef/spec/unit/knife/node_show_spec.rb +++ b/chef/spec/unit/knife/node_show_spec.rb @@ -26,7 +26,7 @@ describe Chef::Knife::NodeShow do :run_list => nil } @knife.name_args = [ "adam" ] - @knife.stub!(:json_pretty_print).and_return(true) + @knife.stub!(:output).and_return(true) @node = Chef::Node.new() Chef::Node.stub!(:load).and_return(@node) end @@ -39,7 +39,7 @@ describe Chef::Knife::NodeShow do 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.should_receive(:output).with("poop") @knife.run end end diff --git a/chef/spec/unit/knife/role_bulk_delete_spec.rb b/chef/spec/unit/knife/role_bulk_delete_spec.rb index 2468728474..8d3d724290 100644 --- a/chef/spec/unit/knife/role_bulk_delete_spec.rb +++ b/chef/spec/unit/knife/role_bulk_delete_spec.rb @@ -25,7 +25,7 @@ describe Chef::Knife::RoleBulkDelete do :print_after => nil } @knife.name_args = ["."] - @knife.stub!(:json_pretty_print).and_return(:true) + @knife.stub!(:output).and_return(:true) @knife.stub!(:confirm).and_return(true) @roles = Hash.new %w{dev staging production}.each do |role_name| @@ -45,7 +45,7 @@ describe Chef::Knife::RoleBulkDelete do end it "should print the roles you are about to delete" do - @knife.should_receive(:json_pretty_print).with(@knife.format_list_for_display(@roles)) + @knife.should_receive(:output).with(@knife.format_list_for_display(@roles)) @knife.run end @@ -78,7 +78,7 @@ describe Chef::Knife::RoleBulkDelete do it "should pretty_print the roles, formatted for display" do @knife.config[:print_after] = true @roles.each_value do |n| - @knife.should_receive(:json_pretty_print).with(@knife.format_for_display(n)) + @knife.should_receive(:output).with(@knife.format_for_display(n)) end @knife.run end diff --git a/chef/spec/unit/knife/role_create_spec.rb b/chef/spec/unit/knife/role_create_spec.rb index 20fffe8c23..519ceb0e02 100644 --- a/chef/spec/unit/knife/role_create_spec.rb +++ b/chef/spec/unit/knife/role_create_spec.rb @@ -25,7 +25,7 @@ describe Chef::Knife::RoleCreate do :description => nil } @knife.name_args = [ "adam" ] - @knife.stub!(:json_pretty_print).and_return(true) + @knife.stub!(:output).and_return(true) @role = Chef::Role.new() @role.stub!(:save) Chef::Role.stub!(:new).and_return(@role) @@ -44,7 +44,7 @@ describe Chef::Knife::RoleCreate do end it "should not print the role" do - @knife.should_not_receive(:json_pretty_print) + @knife.should_not_receive(:output) @knife.run end @@ -69,7 +69,7 @@ describe Chef::Knife::RoleCreate do 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(:json_pretty_print).with(@role) + @knife.should_receive(:output).with(@role) @knife.run end end diff --git a/chef/spec/unit/knife/role_delete_spec.rb b/chef/spec/unit/knife/role_delete_spec.rb index 45f67df2d1..31ba5607ce 100644 --- a/chef/spec/unit/knife/role_delete_spec.rb +++ b/chef/spec/unit/knife/role_delete_spec.rb @@ -25,7 +25,7 @@ describe Chef::Knife::RoleDelete do :print_after => nil } @knife.name_args = [ "adam" ] - @knife.stub!(:json_pretty_print).and_return(true) + @knife.stub!(:output).and_return(true) @knife.stub!(:confirm).and_return(true) @role = Chef::Role.new() @role.stub!(:destroy).and_return(true) @@ -49,14 +49,14 @@ describe Chef::Knife::RoleDelete do end it "should not print the Role" do - @knife.should_not_receive(:json_pretty_print) + @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(:json_pretty_print) + @knife.should_receive(:output) @knife.run end end diff --git a/chef/spec/unit/knife/role_edit_spec.rb b/chef/spec/unit/knife/role_edit_spec.rb index 83e6938f23..5d7cf1030a 100644 --- a/chef/spec/unit/knife/role_edit_spec.rb +++ b/chef/spec/unit/knife/role_edit_spec.rb @@ -25,7 +25,7 @@ describe Chef::Knife::RoleEdit do :print_after => nil } @knife.name_args = [ "adam" ] - @knife.stub!(:json_pretty_print).and_return(true) + @knife.stub!(:output).and_return(true) @role = Chef::Role.new() @role.stub!(:save) Chef::Role.stub!(:load).and_return(@role) @@ -51,14 +51,14 @@ describe Chef::Knife::RoleEdit do end it "should not print the node" do - @knife.should_not_receive(:json_pretty_print) + @knife.should_not_receive(:output) @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(:json_pretty_print).with(@role) + @knife.should_receive(:output).with(@role) @knife.run end end diff --git a/chef/spec/unit/knife/role_from_file_spec.rb b/chef/spec/unit/knife/role_from_file_spec.rb index 208a33ef15..6c4363057c 100644 --- a/chef/spec/unit/knife/role_from_file_spec.rb +++ b/chef/spec/unit/knife/role_from_file_spec.rb @@ -25,7 +25,7 @@ describe Chef::Knife::RoleFromFile do :print_after => nil } @knife.name_args = [ "adam.rb" ] - @knife.stub!(:json_pretty_print).and_return(true) + @knife.stub!(:output).and_return(true) @knife.stub!(:confirm).and_return(true) @role = Chef::Role.new() @role.stub!(:save) @@ -39,14 +39,14 @@ describe Chef::Knife::RoleFromFile do end it "should not print the role" do - @knife.should_not_receive(:json_pretty_print) + @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(:json_pretty_print) + @knife.should_receive(:output) @knife.run end end diff --git a/chef/spec/unit/knife/role_list_spec.rb b/chef/spec/unit/knife/role_list_spec.rb index 483dda4848..861e4e8e43 100644 --- a/chef/spec/unit/knife/role_list_spec.rb +++ b/chef/spec/unit/knife/role_list_spec.rb @@ -21,7 +21,7 @@ require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_hel describe Chef::Knife::RoleList do before(:each) do @knife = Chef::Knife::RoleList.new - @knife.stub!(:json_pretty_print).and_return(true) + @knife.stub!(:output).and_return(true) @list = { "foo" => "http://example.com/foo", "bar" => "http://example.com/foo" @@ -37,7 +37,7 @@ describe Chef::Knife::RoleList do it "should pretty print the list" do Chef::Role.should_receive(:list).and_return(@list) - @knife.should_receive(:json_pretty_print).with([ "bar", "foo" ]) + @knife.should_receive(:output).with([ "bar", "foo" ]) @knife.run end @@ -45,7 +45,7 @@ describe Chef::Knife::RoleList do it "should pretty print the hash" do @knife.config[:with_uri] = true Chef::Role.should_receive(:list).and_return(@list) - @knife.should_receive(:json_pretty_print).with(@list) + @knife.should_receive(:output).with(@list) @knife.run end end diff --git a/chef/spec/unit/knife_spec.rb b/chef/spec/unit/knife_spec.rb index 4953db17f0..45b330906b 100644 --- a/chef/spec/unit/knife_spec.rb +++ b/chef/spec/unit/knife_spec.rb @@ -6,9 +6,9 @@ # 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. @@ -98,8 +98,8 @@ describe Chef::Knife do it "should exit 10 if the sub command is not found" do Chef::Knife.stub!(:list_commands).and_return(true) Chef::Log.should_receive(:fatal) - lambda { - Chef::Knife.find_command([ "monkey", "man" ]) + lambda { + Chef::Knife.find_command([ "monkey", "man" ]) }.should raise_error(SystemExit) { |e| e.status.should == 10 } end end @@ -119,7 +119,7 @@ describe Chef::Knife do 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 ] + @knife.format_list_for_display({ :marcy => :playground }).should == [ :marcy ] end end @@ -141,13 +141,13 @@ describe Chef::Knife 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" } + @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 = Chef::Node.new input.run_list("role[monkey]", "role[churchmouse]") @knife.config[:run_list] = true response = @knife.format_for_display(input) @@ -176,16 +176,16 @@ describe Chef::Knife do it "should exit 3 if you answer N" do STDIN.stub!(:readline).and_return("N") - lambda { + lambda { @knife.confirm(@question) - }.should raise_error(SystemExit) { |e| e.status.should == 3 } + }.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 { + lambda { @knife.confirm(@question) - }.should raise_error(SystemExit) { |e| e.status.should == 3 } + }.should raise_error(SystemExit) { |e| e.status.should == 3 } end describe "with --y or --yes passed" do @@ -195,6 +195,24 @@ describe Chef::Knife do 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 + @knife.stub!(:stdout).and_return(out) + @knife.stub!(:stdin).and_return(StringIO.new("http://mychefserver.example.com\n")) + @knife.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 + @knife.stub!(:stdout).and_return(out) + @knife.stub!(:stdin).and_return(StringIO.new(" \n")) + @knife.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/chef/spec/unit/lwrp_spec.rb b/chef/spec/unit/lwrp_spec.rb index efd9875035..bde2b18c88 100644 --- a/chef/spec/unit/lwrp_spec.rb +++ b/chef/spec/unit/lwrp_spec.rb @@ -18,11 +18,11 @@ require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper")) -Dir[File.join(File.dirname(__FILE__), "..", "data", "lwrp", "resources", "*")].each do |file| +Dir[File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "lwrp", "resources", "*"))].each do |file| Chef::Resource.build_from_file("lwrp", file) end -Dir[File.join(File.dirname(__FILE__), "..", "data", "lwrp", "providers", "*")].each do |file| +Dir[File.expand_path(File.join(File.dirname(__FILE__), "..", "data", "lwrp", "providers", "*"))].each do |file| Chef::Provider.build_from_file("lwrp", file) end @@ -41,7 +41,7 @@ describe Chef::Resource do end it "should create a method for each attribute" do - Chef::Resource::LwrpFoo.new("blah").methods.should include("monkey") + Chef::Resource::LwrpFoo.new("blah").methods.map{ |m| m.to_sym}.should include(:monkey) end it "should build attribute methods that respect validation rules" do @@ -58,8 +58,8 @@ describe Chef::Provider do it "should create a method for each attribute" do new_resource = mock("new resource", :null_object=>true) - Chef::Provider::LwrpBuckPasser.new(nil, new_resource).methods.should include("action_pass_buck") - Chef::Provider::LwrpThumbTwiddler.new(nil, new_resource).methods.should include("action_twiddle_thumbs") + Chef::Provider::LwrpBuckPasser.new(nil, new_resource).methods.map{|m|m.to_sym}.should include(:action_pass_buck) + Chef::Provider::LwrpThumbTwiddler.new(nil, new_resource).methods.map{|m|m.to_sym}.should include(:action_twiddle_thumbs) end it "should insert resources embedded in the provider into the middle of the resource collection" do diff --git a/chef/spec/unit/mixin/checksum_spec.rb b/chef/spec/unit/mixin/checksum_spec.rb index 268cecd356..a9c5a760aa 100644 --- a/chef/spec/unit/mixin/checksum_spec.rb +++ b/chef/spec/unit/mixin/checksum_spec.rb @@ -28,7 +28,7 @@ describe Chef::Mixin::Checksum do before(:each) do @checksum_user = Chef::CMCCheck.new @cache = Chef::Cache::Checksum.instance - @file = File.dirname(__FILE__) + "/../../data/checksum/random.txt" + @file = CHEF_SPEC_DATA + "/checksum/random.txt" @stat = mock("File::Stat", { :mtime => Time.at(0) }) File.stub!(:stat).and_return(@stat) end diff --git a/chef/spec/unit/mixin/deep_merge_spec.rb b/chef/spec/unit/mixin/deep_merge_spec.rb index eba89cb8a4..4486d8d963 100644 --- a/chef/spec/unit/mixin/deep_merge_spec.rb +++ b/chef/spec/unit/mixin/deep_merge_spec.rb @@ -8,9 +8,9 @@ # 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. @@ -26,190 +26,195 @@ require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_hel # Test coverage from the original author converted to rspec describe Chef::Mixin::DeepMerge, "deep_merge!" do - DM = Chef::Mixin::DeepMerge - FIELD_KNOCKOUT_PREFIX = Chef::Mixin::DeepMerge::DEFAULT_FIELD_KNOCKOUT_PREFIX + before do + @dm = Chef::Mixin::DeepMerge + #FIELD_KNOCKOUT_PREFIX = Chef::Mixin::DeepMerge::DEFAULT_FIELD_KNOCKOUT_PREFIX + @field_ko_prefix = Chef::Mixin::DeepMerge::DEFAULT_FIELD_KNOCKOUT_PREFIX + end + #@dm = Chef::Mixin::DeepMerge + # deep_merge core tests - moving from basic to more complex it "tests merging an hash w/array into blank hash" do hash_src = {'id' => '2'} hash_dst = {} - DM.deep_merge!(hash_src.dup, hash_dst, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) + @dm.deep_merge!(hash_src.dup, hash_dst, {:knockout_prefix => @field_ko_prefix, :unpack_arrays => ","}) hash_dst.should == hash_src end it "tests merging an hash w/array into blank hash" do hash_src = {'region' => {'id' => ['227', '2']}} hash_dst = {} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => @field_ko_prefix, :unpack_arrays => ","}) hash_dst.should == hash_src end it "tests merge from empty hash" do hash_src = {} hash_dst = {"property" => ["2","4"]} - DM.deep_merge!(hash_src, hash_dst) + @dm.deep_merge!(hash_src, hash_dst) hash_dst.should == {"property" => ["2","4"]} end it "tests merge to empty hash" do hash_src = {"property" => ["2","4"]} hash_dst = {} - DM.deep_merge!(hash_src, hash_dst) + @dm.deep_merge!(hash_src, hash_dst) hash_dst.should == {"property" => ["2","4"]} end it "tests simple string overwrite" do hash_src = {"name" => "value"} hash_dst = {"name" => "value1"} - DM.deep_merge!(hash_src, hash_dst) + @dm.deep_merge!(hash_src, hash_dst) hash_dst.should == {"name" => "value"} end it "tests simple string overwrite of empty hash" do hash_src = {"name" => "value"} hash_dst = {} - DM.deep_merge!(hash_src, hash_dst) + @dm.deep_merge!(hash_src, hash_dst) hash_dst.should == hash_src end it "tests hashes holding array" do hash_src = {"property" => ["1","3"]} hash_dst = {"property" => ["2","4"]} - DM.deep_merge!(hash_src, hash_dst) + @dm.deep_merge!(hash_src, hash_dst) hash_dst.should == {"property" => ["2","4","1","3"]} end it "tests hashes holding array (sorted)" do hash_src = {"property" => ["1","3"]} hash_dst = {"property" => ["2","4"]} - DM.deep_merge!(hash_src, hash_dst, {:sort_merged_arrays => true}) + @dm.deep_merge!(hash_src, hash_dst, {:sort_merged_arrays => true}) hash_dst.should == {"property" => ["1","2","3","4"]} end it "tests hashes holding hashes holding arrays (array with duplicate elements is merged with dest then src" do hash_src = {"property" => {"bedroom_count" => ["1", "2"], "bathroom_count" => ["1", "4+"]}} hash_dst = {"property" => {"bedroom_count" => ["3", "2"], "bathroom_count" => ["2"]}} - DM.deep_merge!(hash_src, hash_dst) + @dm.deep_merge!(hash_src, hash_dst) hash_dst.should == {"property" => {"bedroom_count" => ["3","2","1"], "bathroom_count" => ["2", "1", "4+"]}} end it "tests hash holding hash holding array v string (string is overwritten by array)" do hash_src = {"property" => {"bedroom_count" => ["1", "2"], "bathroom_count" => ["1", "4+"]}} hash_dst = {"property" => {"bedroom_count" => "3", "bathroom_count" => ["2"]}} - DM.deep_merge!(hash_src, hash_dst) + @dm.deep_merge!(hash_src, hash_dst) hash_dst.should == {"property" => {"bedroom_count" => ["1", "2"], "bathroom_count" => ["2","1","4+"]}} end it "tests hash holding hash holding array v string (string is NOT overwritten by array)" do hash_src = {"property" => {"bedroom_count" => ["1", "2"], "bathroom_count" => ["1", "4+"]}} hash_dst = {"property" => {"bedroom_count" => "3", "bathroom_count" => ["2"]}} - DM.deep_merge!(hash_src, hash_dst, {:preserve_unmergeables => true}) + @dm.deep_merge!(hash_src, hash_dst, {:preserve_unmergeables => true}) hash_dst.should == {"property" => {"bedroom_count" => "3", "bathroom_count" => ["2","1","4+"]}} end it "tests hash holding hash holding string v array (array is overwritten by string)" do hash_src = {"property" => {"bedroom_count" => "3", "bathroom_count" => ["1", "4+"]}} hash_dst = {"property" => {"bedroom_count" => ["1", "2"], "bathroom_count" => ["2"]}} - DM.deep_merge!(hash_src, hash_dst) + @dm.deep_merge!(hash_src, hash_dst) hash_dst.should == {"property" => {"bedroom_count" => "3", "bathroom_count" => ["2","1","4+"]}} end it "tests hash holding hash holding string v array (array does NOT overwrite string)" do hash_src = {"property" => {"bedroom_count" => "3", "bathroom_count" => ["1", "4+"]}} hash_dst = {"property" => {"bedroom_count" => ["1", "2"], "bathroom_count" => ["2"]}} - DM.deep_merge!(hash_src, hash_dst, {:preserve_unmergeables => true}) + @dm.deep_merge!(hash_src, hash_dst, {:preserve_unmergeables => true}) hash_dst.should == {"property" => {"bedroom_count" => ["1", "2"], "bathroom_count" => ["2","1","4+"]}} end it "tests hash holding hash holding hash v array (array is overwritten by hash)" do hash_src = {"property" => {"bedroom_count" => {"king_bed" => 3, "queen_bed" => 1}, "bathroom_count" => ["1", "4+"]}} hash_dst = {"property" => {"bedroom_count" => ["1", "2"], "bathroom_count" => ["2"]}} - DM.deep_merge!(hash_src, hash_dst) + @dm.deep_merge!(hash_src, hash_dst) hash_dst.should == {"property" => {"bedroom_count" => {"king_bed" => 3, "queen_bed" => 1}, "bathroom_count" => ["2","1","4+"]}} end it "tests hash holding hash holding hash v array (array is NOT overwritten by hash)" do hash_src = {"property" => {"bedroom_count" => {"king_bed" => 3, "queen_bed" => 1}, "bathroom_count" => ["1", "4+"]}} hash_dst = {"property" => {"bedroom_count" => ["1", "2"], "bathroom_count" => ["2"]}} - DM.deep_merge!(hash_src, hash_dst, {:preserve_unmergeables => true}) + @dm.deep_merge!(hash_src, hash_dst, {:preserve_unmergeables => true}) hash_dst.should == {"property" => {"bedroom_count" => ["1", "2"], "bathroom_count" => ["2","1","4+"]}} end it "tests 3 hash layers holding integers (integers are overwritten by source)" do hash_src = {"property" => {"bedroom_count" => {"king_bed" => 3, "queen_bed" => 1}, "bathroom_count" => ["1", "4+"]}} hash_dst = {"property" => {"bedroom_count" => {"king_bed" => 2, "queen_bed" => 4}, "bathroom_count" => ["2"]}} - DM.deep_merge!(hash_src, hash_dst) + @dm.deep_merge!(hash_src, hash_dst) hash_dst.should == {"property" => {"bedroom_count" => {"king_bed" => 3, "queen_bed" => 1}, "bathroom_count" => ["2","1","4+"]}} end it "tests 3 hash layers holding arrays of int (arrays are merged)" do hash_src = {"property" => {"bedroom_count" => {"king_bed" => [3], "queen_bed" => [1]}, "bathroom_count" => ["1", "4+"]}} hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}} - DM.deep_merge!(hash_src, hash_dst) + @dm.deep_merge!(hash_src, hash_dst) hash_dst.should == {"property" => {"bedroom_count" => {"king_bed" => [2,3], "queen_bed" => [4,1]}, "bathroom_count" => ["2","1","4+"]}} end it "tests 1 hash overwriting 3 hash layers holding arrays of int" do hash_src = {"property" => "1"} hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}} - DM.deep_merge!(hash_src, hash_dst) + @dm.deep_merge!(hash_src, hash_dst) hash_dst.should == {"property" => "1"} end it "tests 1 hash NOT overwriting 3 hash layers holding arrays of int" do hash_src = {"property" => "1"} hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}} - DM.deep_merge!(hash_src, hash_dst, {:preserve_unmergeables => true}) + @dm.deep_merge!(hash_src, hash_dst, {:preserve_unmergeables => true}) hash_dst.should == {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}} end it "tests 3 hash layers holding arrays of int (arrays are merged) but second hash's array is overwritten" do hash_src = {"property" => {"bedroom_count" => {"king_bed" => [3], "queen_bed" => [1]}, "bathroom_count" => "1"}} hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}} - DM.deep_merge!(hash_src, hash_dst) + @dm.deep_merge!(hash_src, hash_dst) hash_dst.should == {"property" => {"bedroom_count" => {"king_bed" => [2,3], "queen_bed" => [4,1]}, "bathroom_count" => "1"}} end it "tests 3 hash layers holding arrays of int (arrays are merged) but second hash's array is NOT overwritten" do hash_src = {"property" => {"bedroom_count" => {"king_bed" => [3], "queen_bed" => [1]}, "bathroom_count" => "1"}} hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}} - DM.deep_merge!(hash_src, hash_dst, {:preserve_unmergeables => true}) + @dm.deep_merge!(hash_src, hash_dst, {:preserve_unmergeables => true}) hash_dst.should == {"property" => {"bedroom_count" => {"king_bed" => [2,3], "queen_bed" => [4,1]}, "bathroom_count" => ["2"]}} end it "tests 3 hash layers holding arrays of int, but one holds int. This one overwrites, but the rest merge" do hash_src = {"property" => {"bedroom_count" => {"king_bed" => 3, "queen_bed" => [1]}, "bathroom_count" => ["1"]}} hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}} - DM.deep_merge!(hash_src, hash_dst) + @dm.deep_merge!(hash_src, hash_dst) hash_dst.should == {"property" => {"bedroom_count" => {"king_bed" => 3, "queen_bed" => [4,1]}, "bathroom_count" => ["2","1"]}} end it "tests 3 hash layers holding arrays of int, but source is incomplete." do hash_src = {"property" => {"bedroom_count" => {"king_bed" => [3]}, "bathroom_count" => ["1"]}} hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}} - DM.deep_merge!(hash_src, hash_dst) + @dm.deep_merge!(hash_src, hash_dst) hash_dst.should == {"property" => {"bedroom_count" => {"king_bed" => [2,3], "queen_bed" => [4]}, "bathroom_count" => ["2","1"]}} end it "tests 3 hash layers holding arrays of int, but source is shorter and has new 2nd level ints." do hash_src = {"property" => {"bedroom_count" => {2=>3, "king_bed" => [3]}, "bathroom_count" => ["1"]}} hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}} - DM.deep_merge!(hash_src, hash_dst) + @dm.deep_merge!(hash_src, hash_dst) hash_dst.should == {"property" => {"bedroom_count" => {2=>3, "king_bed" => [2,3], "queen_bed" => [4]}, "bathroom_count" => ["2","1"]}} end it "tests 3 hash layers holding arrays of int, but source is empty" do hash_src = {} hash_dst = {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}} - DM.deep_merge!(hash_src, hash_dst) + @dm.deep_merge!(hash_src, hash_dst) hash_dst.should == {"property" => {"bedroom_count" => {"king_bed" => [2], "queen_bed" => [4]}, "bathroom_count" => ["2"]}} end it "tests 3 hash layers holding arrays of int, but dest is empty" do hash_src = {"property" => {"bedroom_count" => {2=>3, "king_bed" => [3]}, "bathroom_count" => ["1"]}} hash_dst = {} - DM.deep_merge!(hash_src, hash_dst) + @dm.deep_merge!(hash_src, hash_dst) hash_dst.should == {"property" => {"bedroom_count" => {2=>3, "king_bed" => [3]}, "bathroom_count" => ["1"]}} end @@ -218,51 +223,51 @@ describe Chef::Mixin::DeepMerge, "deep_merge!" do hash_dst = {"y" => 2} lambda { - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => ""}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => ""}) }.should raise_error(Chef::Mixin::DeepMerge::InvalidParameter) lambda { - DM.deep_merge!(hash_src, hash_dst, {:preserve_unmergeables => true, :knockout_prefix => ""}) + @dm.deep_merge!(hash_src, hash_dst, {:preserve_unmergeables => true, :knockout_prefix => ""}) }.should raise_error(Chef::Mixin::DeepMerge::InvalidParameter) lambda { - DM.deep_merge!(hash_src, hash_dst, {:preserve_unmergeables => true, :knockout_prefix => "--"}) + @dm.deep_merge!(hash_src, hash_dst, {:preserve_unmergeables => true, :knockout_prefix => "--"}) }.should raise_error(Chef::Mixin::DeepMerge::InvalidParameter) lambda { - DM.deep_merge!(DM.deep_merge!(hash_src, hash_dst)) + @dm.deep_merge!(@dm.deep_merge!(hash_src, hash_dst)) }.should_not raise_error(Chef::Mixin::DeepMerge::InvalidParameter) lambda { - DM.deep_merge!(hash_src, hash_dst, {:preserve_unmergeables => true}) + @dm.deep_merge!(hash_src, hash_dst, {:preserve_unmergeables => true}) }.should_not raise_error(Chef::Mixin::DeepMerge::InvalidParameter) end it "tests hash holding arrays of arrays" do hash_src = {["1", "2", "3"] => ["1", "2"]} hash_dst = {["4", "5"] => ["3"]} - DM.deep_merge!(hash_src, hash_dst) + @dm.deep_merge!(hash_src, hash_dst) hash_dst.should == {["1","2","3"] => ["1", "2"], ["4", "5"] => ["3"]} end it "tests merging of hash with blank hash, and make sure that source array split still functions" do hash_src = {'property' => {'bedroom_count' => ["1","2,3"]}} hash_dst = {} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => @field_ko_prefix, :unpack_arrays => ","}) hash_dst.should == {'property' => {'bedroom_count' => ["1","2","3"]}} end it "tests merging of hash with blank hash, and make sure that source array split does not function when turned off" do hash_src = {'property' => {'bedroom_count' => ["1","2,3"]}} hash_dst = {} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => @field_ko_prefix}) hash_dst.should == {'property' => {'bedroom_count' => ["1","2,3"]}} end it "tests merging into a blank hash with overwrite_unmergeables turned on" do hash_src = {"action"=>"browse", "controller"=>"results"} hash_dst = {} - DM.deep_merge!(hash_src, hash_dst, {:overwrite_unmergeables => true, :knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) + @dm.deep_merge!(hash_src, hash_dst, {:overwrite_unmergeables => true, :knockout_prefix => @field_ko_prefix, :unpack_arrays => ","}) hash_dst.should == hash_src end @@ -272,115 +277,115 @@ describe Chef::Mixin::DeepMerge, "deep_merge!" do [nil, ","].each do |ko_split| it "tests typical params/session style hash with knockout_merge elements" do - hash_src = {"property"=>{"bedroom_count"=>[FIELD_KNOCKOUT_PREFIX+"1", "2", "3"]}} + hash_src = {"property"=>{"bedroom_count"=>[@field_ko_prefix+"1", "2", "3"]}} hash_dst = {"property"=>{"bedroom_count"=>["1", "2", "3"]}} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ko_split}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => @field_ko_prefix, :unpack_arrays => ko_split}) hash_dst.should == {"property"=>{"bedroom_count"=>["2", "3"]}} end it "tests typical params/session style hash with knockout_merge elements" do - hash_src = {"property"=>{"bedroom_count"=>[FIELD_KNOCKOUT_PREFIX+"1", "2", "3"]}} + hash_src = {"property"=>{"bedroom_count"=>[@field_ko_prefix+"1", "2", "3"]}} hash_dst = {"property"=>{"bedroom_count"=>["3"]}} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ko_split}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => @field_ko_prefix, :unpack_arrays => ko_split}) hash_dst.should == {"property"=>{"bedroom_count"=>["3","2"]}} end it "tests typical params/session style hash with knockout_merge elements" do - hash_src = {"property"=>{"bedroom_count"=>[FIELD_KNOCKOUT_PREFIX+"1", "2", "3"]}} + hash_src = {"property"=>{"bedroom_count"=>[@field_ko_prefix+"1", "2", "3"]}} hash_dst = {"property"=>{"bedroom_count"=>["4"]}} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ko_split}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => @field_ko_prefix, :unpack_arrays => ko_split}) hash_dst.should == {"property"=>{"bedroom_count"=>["4","2","3"]}} end it "tests typical params/session style hash with knockout_merge elements" do - hash_src = {"property"=>{"bedroom_count"=>[FIELD_KNOCKOUT_PREFIX+"1", "2", "3"]}} - hash_dst = {"property"=>{"bedroom_count"=>[FIELD_KNOCKOUT_PREFIX+"1", "4"]}} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ko_split}) + hash_src = {"property"=>{"bedroom_count"=>[@field_ko_prefix+"1", "2", "3"]}} + hash_dst = {"property"=>{"bedroom_count"=>[@field_ko_prefix+"1", "4"]}} + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => @field_ko_prefix, :unpack_arrays => ko_split}) hash_dst.should == {"property"=>{"bedroom_count"=>["4","2","3"]}} end it "tests typical params/session style hash with knockout_merge elements" do - hash_src = {"amenity"=>{"id"=>[FIELD_KNOCKOUT_PREFIX+"1", FIELD_KNOCKOUT_PREFIX+"2", "3", "4"]}} + hash_src = {"amenity"=>{"id"=>[@field_ko_prefix+"1", @field_ko_prefix+"2", "3", "4"]}} hash_dst = {"amenity"=>{"id"=>["1", "2"]}} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ko_split}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => @field_ko_prefix, :unpack_arrays => ko_split}) hash_dst.should == {"amenity"=>{"id"=>["3","4"]}} end end it "tests special params/session style hash with knockout_merge elements in form src: [\"1\",\"2\"] dest:[\"--1,--2\", \"3,4\"]" do - hash_src = {"amenity"=>{"id"=>[FIELD_KNOCKOUT_PREFIX+"1,"+FIELD_KNOCKOUT_PREFIX+"2", "3,4"]}} + hash_src = {"amenity"=>{"id"=>[@field_ko_prefix+"1,"+@field_ko_prefix+"2", "3,4"]}} hash_dst = {"amenity"=>{"id"=>["1", "2"]}} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => @field_ko_prefix, :unpack_arrays => ","}) hash_dst.should == {"amenity"=>{"id"=>["3","4"]}} end it "tests same as previous but without ko_split value, this merge should fail" do - hash_src = {"amenity"=>{"id"=>[FIELD_KNOCKOUT_PREFIX+"1,"+FIELD_KNOCKOUT_PREFIX+"2", "3,4"]}} + hash_src = {"amenity"=>{"id"=>[@field_ko_prefix+"1,"+@field_ko_prefix+"2", "3,4"]}} hash_dst = {"amenity"=>{"id"=>["1", "2"]}} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => @field_ko_prefix}) hash_dst.should == {"amenity"=>{"id"=>["1","2","3,4"]}} end it "tests special params/session style hash with knockout_merge elements in form src: [\"1\",\"2\"] dest:[\"--1,--2\", \"3,4\"]" do - hash_src = {"amenity"=>{"id"=>[FIELD_KNOCKOUT_PREFIX+"1,2", "3,4", "--5", "6"]}} + hash_src = {"amenity"=>{"id"=>[@field_ko_prefix+"1,2", "3,4", "--5", "6"]}} hash_dst = {"amenity"=>{"id"=>["1", "2"]}} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => @field_ko_prefix, :unpack_arrays => ","}) hash_dst.should == {"amenity"=>{"id"=>["2","3","4","6"]}} end it "tests special params/session style hash with knockout_merge elements in form src: [\"--1,--2\", \"3,4\", \"--5\", \"6\"] dest:[\"1,2\", \"3,4\"]" do - hash_src = {"amenity"=>{"id"=>["#{FIELD_KNOCKOUT_PREFIX}1,#{FIELD_KNOCKOUT_PREFIX}2", "3,4", "#{FIELD_KNOCKOUT_PREFIX}5", "6"]}} + hash_src = {"amenity"=>{"id"=>["#{@field_ko_prefix}1,#{@field_ko_prefix}2", "3,4", "#{@field_ko_prefix}5", "6"]}} hash_dst = {"amenity"=>{"id"=>["1", "2", "3", "4"]}} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => @field_ko_prefix, :unpack_arrays => ","}) hash_dst.should == {"amenity"=>{"id"=>["3","4","6"]}} end it "unamed upstream - tbd" do hash_src = {"url_regions"=>[], "region"=>{"ids"=>["227,233"]}, "action"=>"browse", "task"=>"browse", "controller"=>"results"} hash_dst = {"region"=>{"ids"=>["227"]}} - DM.deep_merge!(hash_src.dup, hash_dst, {:overwrite_unmergeables => true, :knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) + @dm.deep_merge!(hash_src.dup, hash_dst, {:overwrite_unmergeables => true, :knockout_prefix => @field_ko_prefix, :unpack_arrays => ","}) hash_dst.should == {"url_regions"=>[], "region"=>{"ids"=>["227","233"]}, "action"=>"browse", "task"=>"browse", "controller"=>"results"} end it "unamed upstream - tbd" do hash_src = {"region"=>{"ids"=>["--","227"], "id"=>"230"}} hash_dst = {"region"=>{"ids"=>["227", "233", "324", "230", "230"], "id"=>"230"}} - DM.deep_merge!(hash_src, hash_dst, {:overwrite_unmergeables => true, :knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) + @dm.deep_merge!(hash_src, hash_dst, {:overwrite_unmergeables => true, :knockout_prefix => @field_ko_prefix, :unpack_arrays => ","}) hash_dst.should == {"region"=>{"ids"=>["227"], "id"=>"230"}} end it "unamed upstream - tbd" do hash_src = {"region"=>{"ids"=>["--","227", "232", "233"], "id"=>"232"}} hash_dst = {"region"=>{"ids"=>["227", "233", "324", "230", "230"], "id"=>"230"}} - DM.deep_merge!(hash_src, hash_dst, {:overwrite_unmergeables => true, :knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) + @dm.deep_merge!(hash_src, hash_dst, {:overwrite_unmergeables => true, :knockout_prefix => @field_ko_prefix, :unpack_arrays => ","}) hash_dst.should == {"region"=>{"ids"=>["227", "232", "233"], "id"=>"232"}} end it "unamed upstream - tbd" do hash_src = {"region"=>{"ids"=>["--,227,232,233"], "id"=>"232"}} hash_dst = {"region"=>{"ids"=>["227", "233", "324", "230", "230"], "id"=>"230"}} - DM.deep_merge!(hash_src, hash_dst, {:overwrite_unmergeables => true, :knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) + @dm.deep_merge!(hash_src, hash_dst, {:overwrite_unmergeables => true, :knockout_prefix => @field_ko_prefix, :unpack_arrays => ","}) hash_dst.should == {"region"=>{"ids"=>["227", "232", "233"], "id"=>"232"}} end it "unamed upstream - tbd" do hash_src = {"region"=>{"ids"=>["--,227,232","233"], "id"=>"232"}} hash_dst = {"region"=>{"ids"=>["227", "233", "324", "230", "230"], "id"=>"230"}} - DM.deep_merge!(hash_src, hash_dst, {:overwrite_unmergeables => true, :knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) + @dm.deep_merge!(hash_src, hash_dst, {:overwrite_unmergeables => true, :knockout_prefix => @field_ko_prefix, :unpack_arrays => ","}) hash_dst.should == {"region"=>{"ids"=>["227", "232", "233"], "id"=>"232"}} end it "unamed upstream - tbd" do hash_src = {"region"=>{"ids"=>["--,227"], "id"=>"230"}} hash_dst = {"region"=>{"ids"=>["227", "233", "324", "230", "230"], "id"=>"230"}} - DM.deep_merge!(hash_src, hash_dst, {:overwrite_unmergeables => true, :knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) + @dm.deep_merge!(hash_src, hash_dst, {:overwrite_unmergeables => true, :knockout_prefix => @field_ko_prefix, :unpack_arrays => ","}) hash_dst.should == {"region"=>{"ids"=>["227"], "id"=>"230"}} end it "unamed upstream - tbd" do hash_src = {"region"=>{"ids"=>["--,227"], "id"=>"230"}} hash_dst = {"region"=>{"ids"=>["227", "233", "324", "230", "230"], "id"=>"230"}, "action"=>"browse", "task"=>"browse", "controller"=>"results", "property_order_by"=>"property_type.descr"} - DM.deep_merge!(hash_src, hash_dst, {:overwrite_unmergeables => true, :knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) + @dm.deep_merge!(hash_src, hash_dst, {:overwrite_unmergeables => true, :knockout_prefix => @field_ko_prefix, :unpack_arrays => ","}) hash_dst.should == {"region"=>{"ids"=>["227"], "id"=>"230"}, "action"=>"browse", "task"=>"browse", "controller"=>"results", "property_order_by"=>"property_type.descr"} end @@ -388,7 +393,7 @@ describe Chef::Mixin::DeepMerge, "deep_merge!" do it "unamed upstream - tbd" do hash_src = {"query_uuid"=>"6386333d-389b-ab5c-8943-6f3a2aa914d7", "region"=>{"ids"=>["--,227"], "id"=>"230"}} hash_dst = {"query_uuid"=>"6386333d-389b-ab5c-8943-6f3a2aa914d7", "url_regions"=>[], "region"=>{"ids"=>["227", "233", "324", "230", "230"], "id"=>"230"}, "action"=>"browse", "task"=>"browse", "controller"=>"results", "property_order_by"=>"property_type.descr"} - DM.deep_merge!(hash_src, hash_dst, {:overwrite_unmergeables => true, :knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) + @dm.deep_merge!(hash_src, hash_dst, {:overwrite_unmergeables => true, :knockout_prefix => @field_ko_prefix, :unpack_arrays => ","}) hash_dst.should == {"query_uuid" => "6386333d-389b-ab5c-8943-6f3a2aa914d7", "url_regions"=>[], "region"=>{"ids"=>["227"], "id"=>"230"}, "action"=>"browse", "task"=>"browse", "controller"=>"results", "property_order_by"=>"property_type.descr"} @@ -397,273 +402,273 @@ describe Chef::Mixin::DeepMerge, "deep_merge!" do it "tests knock out entire dest hash if \"--\" is passed for source" do hash_src = {'amenity' => "--"} hash_dst = {"amenity" => "1"} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => "--", :unpack_arrays => ","}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => "--", :unpack_arrays => ","}) hash_dst.should == {'amenity' => ""} end it "tests knock out entire dest hash if \"--\" is passed for source" do hash_src = {'amenity' => ["--"]} hash_dst = {"amenity" => "1"} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => "--", :unpack_arrays => ","}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => "--", :unpack_arrays => ","}) hash_dst.should == {'amenity' => []} end it "tests knock out entire dest hash if \"--\" is passed for source" do hash_src = {'amenity' => "--"} hash_dst = {"amenity" => ["1"]} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => "--", :unpack_arrays => ","}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => "--", :unpack_arrays => ","}) hash_dst.should == {'amenity' => ""} end it "tests knock out entire dest hash if \"--\" is passed for source" do hash_src = {'amenity' => ["--"]} hash_dst = {"amenity" => ["1"]} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => "--", :unpack_arrays => ","}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => "--", :unpack_arrays => ","}) hash_dst.should == {'amenity' => []} end it "tests knock out entire dest hash if \"--\" is passed for source" do hash_src = {'amenity' => ["--"]} hash_dst = {"amenity" => "1"} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => "--", :unpack_arrays => ","}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => "--", :unpack_arrays => ","}) hash_dst.should == {'amenity' => []} end it "tests knock out entire dest hash if \"--\" is passed for source" do hash_src = {'amenity' => ["--", "2"]} hash_dst = {'amenity' => ["1", "3", "7+"]} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => "--", :unpack_arrays => ","}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => "--", :unpack_arrays => ","}) hash_dst.should == {'amenity' => ["2"]} end it "tests knock out entire dest hash if \"--\" is passed for source" do hash_src = {'amenity' => ["--", "2"]} hash_dst = {'amenity' => "5"} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => "--", :unpack_arrays => ","}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => "--", :unpack_arrays => ","}) hash_dst.should == {'amenity' => ['2']} end it "tests knock out entire dest hash if \"--\" is passed for source" do hash_src = {'amenity' => "--"} hash_dst = {"amenity"=>{"id"=>["1", "2", "3", "4"]}} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => "--", :unpack_arrays => ","}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => "--", :unpack_arrays => ","}) hash_dst.should == {'amenity' => ""} end it "tests knock out entire dest hash if \"--\" is passed for source" do hash_src = {'amenity' => ["--"]} hash_dst = {"amenity"=>{"id"=>["1", "2", "3", "4"]}} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => "--", :unpack_arrays => ","}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => "--", :unpack_arrays => ","}) hash_dst.should == {'amenity' => []} end it "tests knock out dest array if \"--\" is passed for source" do - hash_src = {"region" => {'ids' => FIELD_KNOCKOUT_PREFIX}} + hash_src = {"region" => {'ids' => @field_ko_prefix}} hash_dst = {"region"=>{"ids"=>["1", "2", "3", "4"]}} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => @field_ko_prefix, :unpack_arrays => ","}) hash_dst.should == {'region' => {'ids' => ""}} end it "tests knock out dest array but leave other elements of hash intact" do - hash_src = {"region" => {'ids' => FIELD_KNOCKOUT_PREFIX}} + hash_src = {"region" => {'ids' => @field_ko_prefix}} hash_dst = {"region"=>{"ids"=>["1", "2", "3", "4"], 'id'=>'11'}} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => @field_ko_prefix, :unpack_arrays => ","}) hash_dst.should == {'region' => {'ids' => "", 'id'=>'11'}} end it "tests knock out entire tree of dest hash" do - hash_src = {"region" => FIELD_KNOCKOUT_PREFIX} + hash_src = {"region" => @field_ko_prefix} hash_dst = {"region"=>{"ids"=>["1", "2", "3", "4"], 'id'=>'11'}} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => @field_ko_prefix, :unpack_arrays => ","}) hash_dst.should == {'region' => ""} end it "tests knock out entire tree of dest hash - retaining array format" do - hash_src = {"region" => {'ids' => [FIELD_KNOCKOUT_PREFIX]}} + hash_src = {"region" => {'ids' => [@field_ko_prefix]}} hash_dst = {"region"=>{"ids"=>["1", "2", "3", "4"], 'id'=>'11'}} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => @field_ko_prefix, :unpack_arrays => ","}) hash_dst.should == {'region' => {'ids' => [], 'id'=>'11'}} end it "tests knock out entire tree of dest hash & replace with new content" do - hash_src = {"region" => {'ids' => ["2", FIELD_KNOCKOUT_PREFIX, "6"]}} + hash_src = {"region" => {'ids' => ["2", @field_ko_prefix, "6"]}} hash_dst = {"region"=>{"ids"=>["1", "2", "3", "4"], 'id'=>'11'}} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => @field_ko_prefix, :unpack_arrays => ","}) hash_dst.should == {'region' => {'ids' => ["2", "6"], 'id'=>'11'}} end it "tests knock out entire tree of dest hash & replace with new content" do - hash_src = {"region" => {'ids' => ["7", FIELD_KNOCKOUT_PREFIX, "6"]}} + hash_src = {"region" => {'ids' => ["7", @field_ko_prefix, "6"]}} hash_dst = {"region"=>{"ids"=>["1", "2", "3", "4"], 'id'=>'11'}} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => @field_ko_prefix, :unpack_arrays => ","}) hash_dst.should == {'region' => {'ids' => ["7", "6"], 'id'=>'11'}} end it "tests edge test: make sure that when we turn off knockout_prefix that all values are processed correctly" do hash_src = {"region" => {'ids' => ["7", "--", "2", "6,8"]}} hash_dst = {"region"=>{"ids"=>["1", "2", "3", "4"], 'id'=>'11'}} - DM.deep_merge!(hash_src, hash_dst, {:unpack_arrays => ","}) + @dm.deep_merge!(hash_src, hash_dst, {:unpack_arrays => ","}) hash_dst.should == {'region' => {'ids' => ["1", "2", "3", "4", "7", "--", "6", "8"], 'id'=>'11'}} end it "tests edge test 2: make sure that when we turn off source array split that all values are processed correctly" do hash_src = {"region" => {'ids' => ["7", "3", "--", "6,8"]}} hash_dst = {"region"=>{"ids"=>["1", "2", "3", "4"], 'id'=>'11'}} - DM.deep_merge!(hash_src, hash_dst) + @dm.deep_merge!(hash_src, hash_dst) hash_dst.should == {'region' => {'ids' => ["1", "2", "3", "4", "7", "--", "6,8"], 'id'=>'11'}} end it "tests Example: src = {'key' => \"--1\"}, dst = {'key' => \"1\"} -> merges to {'key' => \"\"}" do hash_src = {"amenity"=>"--1"} hash_dst = {"amenity"=>"1"} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => @field_ko_prefix}) hash_dst.should == {"amenity"=>""} end it "tests Example: src = {'key' => \"--1\"}, dst = {'key' => \"2\"} -> merges to {'key' => \"\"}" do hash_src = {"amenity"=>"--1"} hash_dst = {"amenity"=>"2"} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => @field_ko_prefix}) hash_dst.should == {"amenity"=>""} end it "tests Example: src = {'key' => \"--1\"}, dst = {'key' => \"1\"} -> merges to {'key' => \"\"}" do hash_src = {"amenity"=>["--1"]} hash_dst = {"amenity"=>"1"} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => @field_ko_prefix}) hash_dst.should == {"amenity"=>[]} end it "tests Example: src = {'key' => \"--1\"}, dst = {'key' => \"1\"} -> merges to {'key' => \"\"}" do hash_src = {"amenity"=>["--1"]} hash_dst = {"amenity"=>["1"]} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => @field_ko_prefix}) hash_dst.should == {"amenity"=>[]} end it "tests Example: src = {'key' => \"--1\"}, dst = {'key' => \"1\"} -> merges to {'key' => \"\"}" do hash_src = {"amenity"=>"--1"} hash_dst = {} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => @field_ko_prefix}) hash_dst.should == {"amenity"=>""} end it "tests Example: src = {'key' => \"--1\"}, dst = {'key' => \"1\"} -> merges to {'key' => \"\"}" do hash_src = {"amenity"=>"--1"} hash_dst = {"amenity"=>["1"]} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => @field_ko_prefix}) hash_dst.should == {"amenity"=>""} end it "tests are unmerged hashes passed unmodified w/out :unpack_arrays?" do hash_src = {"amenity"=>{"id"=>["26,27"]}} hash_dst = {} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => @field_ko_prefix}) hash_dst.should == {"amenity"=>{"id"=>["26,27"]}} end it "tests hash should be merged" do hash_src = {"amenity"=>{"id"=>["26,27"]}} hash_dst = {} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => @field_ko_prefix, :unpack_arrays => ","}) hash_dst.should == {"amenity"=>{"id"=>["26","27"]}} end it "tests second merge of same values should result in no change in output" do hash_src = {"amenity"=>{"id"=>["26,27"]}} hash_dst = {} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => @field_ko_prefix, :unpack_arrays => ","}) hash_dst.should == {"amenity"=>{"id"=>["26","27"]}} end it "tests hashes with knockout values are suppressed" do - hash_src = {"amenity"=>{"id"=>["#{FIELD_KNOCKOUT_PREFIX}26,#{FIELD_KNOCKOUT_PREFIX}27,28"]}} + hash_src = {"amenity"=>{"id"=>["#{@field_ko_prefix}26,#{@field_ko_prefix}27,28"]}} hash_dst = {} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => FIELD_KNOCKOUT_PREFIX, :unpack_arrays => ","}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => @field_ko_prefix, :unpack_arrays => ","}) hash_dst.should == {"amenity"=>{"id"=>["28"]}} end it "unamed upstream - tbd" do hash_src= {'region' =>{'ids'=>['--']}, 'query_uuid' => 'zzz'} hash_dst= {'region' =>{'ids'=>['227','2','3','3']}, 'query_uuid' => 'zzz'} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => '--', :unpack_arrays => ","}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => '--', :unpack_arrays => ","}) hash_dst.should == {'region' =>{'ids'=>[]}, 'query_uuid' => 'zzz'} end it "unamed upstream - tbd" do hash_src= {'region' =>{'ids'=>['--']}, 'query_uuid' => 'zzz'} hash_dst= {'region' =>{'ids'=>['227','2','3','3'], 'id' => '3'}, 'query_uuid' => 'zzz'} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => '--', :unpack_arrays => ","}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => '--', :unpack_arrays => ","}) hash_dst.should == {'region' =>{'ids'=>[], 'id'=>'3'}, 'query_uuid' => 'zzz'} end it "unamed upstream - tbd" do hash_src= {'region' =>{'ids'=>['--']}, 'query_uuid' => 'zzz'} hash_dst= {'region' =>{'muni_city_id' => '2244', 'ids'=>['227','2','3','3'], 'id'=>'3'}, 'query_uuid' => 'zzz'} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => '--', :unpack_arrays => ","}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => '--', :unpack_arrays => ","}) hash_dst.should == {'region' =>{'muni_city_id' => '2244', 'ids'=>[], 'id'=>'3'}, 'query_uuid' => 'zzz'} end it "unamed upstream - tbd" do hash_src= {'region' =>{'ids'=>['--'], 'id' => '5'}, 'query_uuid' => 'zzz'} hash_dst= {'region' =>{'muni_city_id' => '2244', 'ids'=>['227','2','3','3'], 'id'=>'3'}, 'query_uuid' => 'zzz'} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => '--', :unpack_arrays => ","}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => '--', :unpack_arrays => ","}) hash_dst.should == {'region' =>{'muni_city_id' => '2244', 'ids'=>[], 'id'=>'5'}, 'query_uuid' => 'zzz'} end it "unamed upstream - tbd" do hash_src= {'region' =>{'ids'=>['--', '227'], 'id' => '5'}, 'query_uuid' => 'zzz'} hash_dst= {'region' =>{'muni_city_id' => '2244', 'ids'=>['227','2','3','3'], 'id'=>'3'}, 'query_uuid' => 'zzz'} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => '--', :unpack_arrays => ","}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => '--', :unpack_arrays => ","}) hash_dst.should == {'region' =>{'muni_city_id' => '2244', 'ids'=>['227'], 'id'=>'5'}, 'query_uuid' => 'zzz'} end it "unamed upstream - tbd" do hash_src= {'region' =>{'muni_city_id' => '--', 'ids'=>'--', 'id'=>'5'}, 'query_uuid' => 'zzz'} hash_dst= {'region' =>{'muni_city_id' => '2244', 'ids'=>['227','2','3','3'], 'id'=>'3'}, 'query_uuid' => 'zzz'} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => '--', :unpack_arrays => ","}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => '--', :unpack_arrays => ","}) hash_dst.should == {'region' =>{'muni_city_id' => '', 'ids'=>'', 'id'=>'5'}, 'query_uuid' => 'zzz'} end it "unamed upstream - tbd" do hash_src= {'region' =>{'muni_city_id' => '--', 'ids'=>['--'], 'id'=>'5'}, 'query_uuid' => 'zzz'} hash_dst= {'region' =>{'muni_city_id' => '2244', 'ids'=>['227','2','3','3'], 'id'=>'3'}, 'query_uuid' => 'zzz'} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => '--', :unpack_arrays => ","}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => '--', :unpack_arrays => ","}) hash_dst.should == {'region' =>{'muni_city_id' => '', 'ids'=>[], 'id'=>'5'}, 'query_uuid' => 'zzz'} end it "unamed upstream - tbd" do hash_src= {'region' =>{'muni_city_id' => '--', 'ids'=>['--','227'], 'id'=>'5'}, 'query_uuid' => 'zzz'} hash_dst= {'region' =>{'muni_city_id' => '2244', 'ids'=>['227','2','3','3'], 'id'=>'3'}, 'query_uuid' => 'zzz'} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => '--', :unpack_arrays => ","}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => '--', :unpack_arrays => ","}) hash_dst.should == {'region' =>{'muni_city_id' => '', 'ids'=>['227'], 'id'=>'5'}, 'query_uuid' => 'zzz'} end it "unamed upstream - tbd" do hash_src = {"muni_city_id"=>"--", "id"=>""} hash_dst = {"muni_city_id"=>"", "id"=>""} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => '--', :unpack_arrays => ","}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => '--', :unpack_arrays => ","}) hash_dst.should == {"muni_city_id"=>"", "id"=>""} end it "unamed upstream - tbd" do hash_src = {"region"=>{"muni_city_id"=>"--", "id"=>""}} hash_dst = {"region"=>{"muni_city_id"=>"", "id"=>""}} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => '--', :unpack_arrays => ","}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => '--', :unpack_arrays => ","}) hash_dst.should == {"region"=>{"muni_city_id"=>"", "id"=>""}} end it "unamed upstream - tbd" do hash_src = {"query_uuid"=>"a0dc3c84-ec7f-6756-bdb0-fff9157438ab", "url_regions"=>[], "region"=>{"muni_city_id"=>"--", "id"=>""}, "property"=>{"property_type_id"=>"", "search_rate_min"=>"", "search_rate_max"=>""}, "task"=>"search", "run_query"=>"Search"} hash_dst = {"query_uuid"=>"a0dc3c84-ec7f-6756-bdb0-fff9157438ab", "url_regions"=>[], "region"=>{"muni_city_id"=>"", "id"=>""}, "property"=>{"property_type_id"=>"", "search_rate_min"=>"", "search_rate_max"=>""}, "task"=>"search", "run_query"=>"Search"} - DM.deep_merge!(hash_src, hash_dst, {:knockout_prefix => '--', :unpack_arrays => ","}) + @dm.deep_merge!(hash_src, hash_dst, {:knockout_prefix => '--', :unpack_arrays => ","}) hash_dst.should == {"query_uuid"=>"a0dc3c84-ec7f-6756-bdb0-fff9157438ab", "url_regions"=>[], "region"=>{"muni_city_id"=>"", "id"=>""}, "property"=>{"property_type_id"=>"", "search_rate_min"=>"", "search_rate_max"=>""}, "task"=>"search", "run_query"=>"Search"} end it "tests hash of array of hashes" do hash_src = {"item" => [{"1" => "3"}, {"2" => "4"}]} hash_dst = {"item" => [{"3" => "5"}]} - DM.deep_merge!(hash_src, hash_dst) + @dm.deep_merge!(hash_src, hash_dst) hash_dst.should == {"item" => [{"3" => "5"}, {"1" => "3"}, {"2" => "4"}]} end @@ -671,79 +676,83 @@ describe Chef::Mixin::DeepMerge, "deep_merge!" do it "should overwrite true with false when merging boolean values" do hash_src = {"valid" => false} hash_dst = {"valid" => true} - DM.deep_merge!(hash_src, hash_dst) + @dm.deep_merge!(hash_src, hash_dst) hash_dst.should == {"valid" => false} end it "should overwrite false with true when merging boolean values" do hash_src = {"valid" => true} hash_dst = {"valid" => false} - DM.deep_merge!(hash_src, hash_dst) + @dm.deep_merge!(hash_src, hash_dst) hash_dst.should == {"valid" => true} end it "should overwrite a string with an empty string when merging string values" do hash_src = {"item" => " "} hash_dst = {"item" => "orange"} - DM.deep_merge!(hash_src, hash_dst) + @dm.deep_merge!(hash_src, hash_dst) hash_dst.should == {"item" => " "} end it "should overwrite an empty string with a string when merging string values" do hash_src = {"item" => "orange"} hash_dst = {"item" => " "} - DM.deep_merge!(hash_src, hash_dst) + @dm.deep_merge!(hash_src, hash_dst) hash_dst.should == {"item" => "orange"} end end # deep_merge! # Chef specific describe Chef::Mixin::DeepMerge, "merge" do + before do + @dm = Chef::Mixin::DeepMerge + end + it "should merge a hash into an empty hash" do hash_dst = {} hash_src = {'id' => '2'} - DM.merge(hash_dst, hash_src).should == hash_src + @dm.merge(hash_dst, hash_src).should == hash_src end it "should merge a nested hash into an empty hash" do hash_dst = {} hash_src = {'region' => {'id' => ['227', '2']}} - DM.merge(hash_dst, hash_src).should == hash_src + @dm.merge(hash_dst, hash_src).should == hash_src end it "should overwrite as string value when merging hashes" do hash_dst = {"name" => "value1"} hash_src = {"name" => "value"} - DM.merge(hash_dst, hash_src).should == {"name" => "value"} + @dm.merge(hash_dst, hash_src).should == {"name" => "value"} end it "should merge arrays within hashes" do hash_dst = {"property" => ["2","4"]} hash_src = {"property" => ["1","3"]} - DM.merge(hash_dst, hash_src).should == {"property" => ["2","4","1","3"]} + @dm.merge(hash_dst, hash_src).should == {"property" => ["2","4","1","3"]} end it "should merge deeply nested hashes" do hash_dst = {"property" => {"values" => {"are" => "falling", "can" => "change"}}} hash_src = {"property" => {"values" => {"are" => "stable", "may" => "rise"}}} - DM.merge(hash_dst, hash_src).should == {"property" => {"values" => {"are" => "stable", "can" => "change", "may" => "rise"}}} + @dm.merge(hash_dst, hash_src).should == {"property" => {"values" => {"are" => "stable", "can" => "change", "may" => "rise"}}} end it "should knockout matching array value when merging arrays within hashes" do hash_dst = {"property" => ["2","4"]} hash_src = {"property" => ["1","!merge:4"]} - DM.merge(hash_dst, hash_src).should == {"property" => ["2","1"]} + @dm.merge(hash_dst, hash_src).should == {"property" => ["2","1"]} end it "should knockout all array values when merging arrays within hashes, leaving 2" do hash_dst = {"property" => ["2","4"]} hash_src = {"property" => ["!merge:","1","2"]} - DM.merge(hash_dst, hash_src).should == {"property" => ["1","2"]} + @dm.merge(hash_dst, hash_src).should == {"property" => ["1","2"]} end it "should knockout all array values when merging arrays within hashes, leaving 0" do hash_dst = {"property" => ["2","4"]} hash_src = {"property" => ["!merge:"]} - DM.merge(hash_dst, hash_src).should == {"property" => []} + @dm.merge(hash_dst, hash_src).should == {"property" => []} end end diff --git a/chef/spec/unit/mixin/template_spec.rb b/chef/spec/unit/mixin/template_spec.rb index bfbf0d561f..277db0e043 100644 --- a/chef/spec/unit/mixin/template_spec.rb +++ b/chef/spec/unit/mixin/template_spec.rb @@ -27,22 +27,27 @@ describe Chef::Mixin::Template, "render_template" do end it "should render the template evaluated in the given context" do - @template.render_template("<%= @foo %>", { :foo => "bar" }).open.read.should == "bar" + @template.render_template("<%= @foo %>", { :foo => "bar" }) do |tmp| + tmp.open.read.should == "bar" + end end it "should provide a node method to access @node" do - @template.render_template("<%= node %>",{:node => "tehShizzle"}).open.read.should == "tehShizzle" + @template.render_template("<%= node %>",{:node => "tehShizzle"}) do |tmp| + tmp.open.read.should == "tehShizzle" + end end - it "should return a file" do - f = @template.render_template("abcdef", {}) - @template.render_template("abcdef", {}).should be_kind_of(Tempfile) + it "should yield the tempfile it renders the template to" do + @template.render_template("abcdef", {}) do |tempfile| + tempfile.should be_kind_of(Tempfile) + end end describe "when an exception is raised in the template" do def do_raise @context = {:chef => "cool"} - @template.render_template("foo\nbar\nbaz\n<%= this_is_not_defined %>\nquin\nqunx\ndunno", @context) + @template.render_template("foo\nbar\nbaz\n<%= this_is_not_defined %>\nquin\nqunx\ndunno", @context) {|r| r} end it "should catch and re-raise the exception as a TemplateError" do @@ -50,7 +55,7 @@ describe Chef::Mixin::Template, "render_template" do end it "should raise an error if an attempt is made to access node but it is nil" do - lambda {@template.render_template("<%= node %>",{})}.should raise_error(Chef::Mixin::Template::TemplateError) + lambda {@template.render_template("<%= node %>",{}) {|r| r}}.should raise_error(Chef::Mixin::Template::TemplateError) end describe "the raised TemplateError" do diff --git a/chef/spec/unit/node_spec.rb b/chef/spec/unit/node_spec.rb index 0e9980c1c1..61034dc8bf 100644 --- a/chef/spec/unit/node_spec.rb +++ b/chef/spec/unit/node_spec.rb @@ -20,7 +20,7 @@ require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper")) describe Chef::Node do before(:each) do - Chef::Config.node_path(File.join(File.dirname(__FILE__), "..", "data", "nodes")) + Chef::Config.node_path(File.expand_path(File.join(CHEF_SPEC_DATA, "nodes"))) @node = Chef::Node.new() end @@ -203,16 +203,34 @@ describe Chef::Node do end it "should set the tags attribute to an empty array if it is not already defined" do - @node.consume_attributes "{}" + @node.consume_attributes({}) @node.tags.should eql([]) end it "should not set the tags attribute to an empty array if it is already defined" do @node[:tags] = [ "radiohead" ] - @node.consume_attributes "{}" + @node.consume_attributes({}) @node.tags.should eql([ "radiohead" ]) end + it "deep merges attributes instead of overwriting them" do + @node.consume_attributes "one" => {"two" => {"three" => "four"}} + @node.one.to_hash.should == {"two" => {"three" => "four"}} + @node.consume_attributes "one" => {"abc" => "123"} + @node.consume_attributes "one" => {"two" => {"foo" => "bar"}} + @node.one.to_hash.should == {"two" => {"three" => "four", "foo" => "bar"}, "abc" => "123"} + end + + it "gives attributes from JSON priority when deep merging" do + @node.consume_attributes "one" => {"two" => {"three" => "four"}} + @node.one.to_hash.should == {"two" => {"three" => "four"}} + @node.consume_attributes "one" => {"two" => {"three" => "forty-two"}} + @node.one.to_hash.should == {"two" => {"three" => "forty-two"}} + end + + it "raises an exception if you provide both recipe and run_list attributes, since this is ambiguous" do + lambda { @node.consume_attributes "recipes" => "stuff", "run_list" => "other_stuff" }.should raise_error(Chef::Exceptions::AmbiguousRunlistSpecification) + end end describe "recipes" do @@ -276,7 +294,7 @@ describe Chef::Node do describe "from file" do it "should load a node from a ruby file" do - @node.from_file(File.join(File.dirname(__FILE__), "..", "data", "nodes", "test.rb")) + @node.from_file(File.expand_path(File.join(CHEF_SPEC_DATA, "nodes", "test.rb"))) @node.name.should eql("test.example.com short") @node.sunshine.should eql("in") @node.something.should eql("else") diff --git a/chef/spec/unit/platform_spec.rb b/chef/spec/unit/platform_spec.rb index 254e8a2716..7e8e9aa76c 100644 --- a/chef/spec/unit/platform_spec.rb +++ b/chef/spec/unit/platform_spec.rb @@ -30,7 +30,10 @@ describe "Chef::Platform supports" do :redhat, :gentoo, :arch, - :solaris + :solaris, + :mswin, + :mingw32, + :windows ].each do |platform| it "#{platform}" do Chef::Platform.platforms.should have_key(platform) diff --git a/chef/spec/unit/provider/cron_spec.rb b/chef/spec/unit/provider/cron_spec.rb index 0a1a230e5c..c6cc9e3557 100644 --- a/chef/spec/unit/provider/cron_spec.rb +++ b/chef/spec/unit/provider/cron_spec.rb @@ -37,13 +37,13 @@ describe Chef::Provider::Cron, "load_current_resource" do @new_resource = mock("Chef::Resource::Cron", :null_object => true, :user => "root", - :name => "foo", + :name => "foo[bar] (baz)", :minute => "30", :command => "/bin/true" ) @current_resource = mock("Chef::Resource::Cron", :null_object => true, - :name => "foo", + :name => "foo[bar] (baz)", :minute => "30", :command => "/bin/true" ) @@ -70,8 +70,7 @@ describe Chef::Provider::Cron, "load_current_resource" do @stdout = mock("STDOUT", :null_object => true) @stderr = mock("STDERR", :null_object => true) @pid = mock("PID", :null_object => true) - @stdout.stub!(:each).and_yield("# Chef Name: foo\n"). - and_yield("* 5 * * * /bin/true\n") + @stdout.stub!(:each_line).and_yield("# Chef Name: foo[bar] (baz)\n").and_yield("* 5 * * * /bin/true\n") @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) Chef::Log.should_receive(:debug).with("Found cron '#{@new_resource.name}'") @provider.load_current_resource @@ -83,7 +82,7 @@ describe Chef::Provider::Cron, "load_current_resource" do @stdout = mock("STDOUT", :null_object => true) @stderr = mock("STDERR", :null_object => true) @pid = mock("PID", :null_object => true) - @stdout.stub!(:each).and_yield("# Chef Name: foo\n"). + @stdout.stub!(:each).and_yield("# Chef Name: foo[bar] (baz)\n"). and_yield("21 */4 * * * some_prog 1234567\n") @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) lambda { @@ -98,7 +97,7 @@ describe Chef::Provider::Cron, "compare_cron" do @new_resource = mock("Chef::Resource::Cron", :null_object => true, :user => "root", - :name => "foo", + :name => "foo[bar] (baz)", :minute => "30", :hour => "2", :day => "30", @@ -113,7 +112,7 @@ describe Chef::Provider::Cron, "compare_cron" do @current_resource = mock("Chef::Resource::Cron", :null_object => true, :user => "root", - :name => "foo", + :name => "foo[bar] (baz)", :minute => "30", :hour => "2", :day => "30", @@ -148,7 +147,7 @@ describe Chef::Provider::Cron, "action_create" do @node = mock("Chef::Node", :null_object => true) @new_resource = mock("Chef::Resource::Cron", :null_object => true, - :name => "foo", + :name => "foo[bar] (baz)", :minute => "30", :hour => "*", :day => "*", @@ -162,7 +161,7 @@ describe Chef::Provider::Cron, "action_create" do ) @current_resource = mock("Chef::Resource::Cron", :null_object => true, - :name => "foo", + :name => "foo[bar] (baz)", :minute => "*", :hour => "5", :day => "*", @@ -198,7 +197,7 @@ describe Chef::Provider::Cron, "action_create" do @pid = mock("PID", :null_object => true) @stdout.stub!(:each_line).and_yield("# Chef Name: bar\n"). and_yield("* 10 * * * /bin/false\n"). - and_yield("# Chef Name: foo\n"). + and_yield("# Chef Name: foo[bar] (baz)\n"). and_yield("* 5 * * * /bin/true\n") @provider.cron_empty=true @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) @@ -215,7 +214,7 @@ describe Chef::Provider::Cron, "action_create" do @pid = mock("PID", :null_object => true) @stdout.stub!(:each_line).and_yield("# Chef Name: bar\n"). and_yield("* 10 * * * /bin/false\n"). - and_yield("# Chef Name: foo\n"). + and_yield("# Chef Name: foo[bar] (baz)\n"). and_yield("* 5 * * * /bin/true\n") @provider.cron_exists=true @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) @@ -232,7 +231,7 @@ describe Chef::Provider::Cron, "action_create" do @pid = mock("PID", :null_object => true) @stdout.stub!(:each_line).and_yield("# Chef Name: bar\n"). and_yield("* 10 * * * /bin/false\n"). - and_yield("# Chef Name: foo\n"). + and_yield("# Chef Name: foo[bar] (baz)\n"). and_yield("30 * * * * /bin/true\n") @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) Chef::Log.should_not_receive(:info).with("Updated cron '#{@new_resource.name}'") @@ -251,7 +250,7 @@ describe Chef::Provider::Cron, "action_create" do @pid = mock("PID", :null_object => true) @stdout.stub!(:each_line).and_yield("# Chef Name: bar\n"). and_yield("* 10 * * * /bin/false\n"). - and_yield("# Chef Name: foo\n"). + and_yield("# Chef Name: foo[bar] (baz)\n"). and_yield("MAILTO=warn@example.com\n"). and_yield("30 * * * * /bin/true\n") @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) @@ -264,7 +263,7 @@ describe Chef::Provider::Cron, "action_create" do it "should update the cron entry if it exists and has no environment variables" do resource = mock("Chef::Resource::Cron", :null_object => true, - :name => "foo", + :name => "foo[bar] (baz)", :minute => "30", :hour => "*", :day => "*", @@ -286,7 +285,7 @@ describe Chef::Provider::Cron, "action_create" do @pid = mock("PID", :null_object => true) @stdout.stub!(:each_line).and_yield("# Chef Name: bar\n"). and_yield("* 10 * * * /bin/false\n"). - and_yield("# Chef Name: foo\n"). + and_yield("# Chef Name: foo[bar] (baz)\n"). and_yield("30 * * * * /bin/true\n") provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) Chef::Log.should_receive(:info).with("Updated cron '#{@new_resource.name}'") @@ -301,13 +300,13 @@ describe Chef::Provider::Cron, "action_delete" do @node = mock("Chef::Node", :null_object => true) @new_resource = mock("Chef::Resource::Cron", :null_object => true, - :name => "foo", + :name => "foo[bar] (baz)", :minute => "30", :command => "/bin/true" ) @current_resource = mock("Chef::Resource::Cron", :null_object => true, - :name => "foo", + :name => "foo[bar] (baz)", :minute => "30", :command => "/bin/true" ) @@ -323,7 +322,7 @@ describe Chef::Provider::Cron, "action_delete" do @pid = mock("PID", :null_object => true) @stdout.stub!(:each_line).and_yield("# Chef Name: bar\n"). and_yield("* 10 * * * /bin/false\n"). - and_yield("# Chef Name: foo\n"). + and_yield("# Chef Name: foo[bar] (baz)\n"). and_yield("* 30 * * * /bin/true\n") @provider.cron_exists=true @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) diff --git a/chef/spec/unit/provider/deploy/revision_spec.rb b/chef/spec/unit/provider/deploy/revision_spec.rb index 5bce4db883..d0f9a188f8 100644 --- a/chef/spec/unit/provider/deploy/revision_spec.rb +++ b/chef/spec/unit/provider/deploy/revision_spec.rb @@ -87,4 +87,13 @@ describe Chef::Provider::Deploy::Revision do @provider.all_releases.should == %w{second third fourth fifth latest} end + it "regenerates the file cache if it's not available" do + oldest = "/my/deploy/dir/releases/oldest" + latest = "/my/deploy/dir/releases/latest" + Dir.should_receive(:glob).with("/my/deploy/dir/releases/*").and_return([latest, oldest]) + ::File.should_receive(:ctime).with(oldest).and_return(Time.now - 10) + ::File.should_receive(:ctime).with(latest).and_return(Time.now - 1) + @provider.all_releases.should == [oldest, latest] + end + end
\ No newline at end of file diff --git a/chef/spec/unit/provider/deploy_spec.rb b/chef/spec/unit/provider/deploy_spec.rb index 30d0c3f0f4..55f56dc252 100644 --- a/chef/spec/unit/provider/deploy_spec.rb +++ b/chef/spec/unit/provider/deploy_spec.rb @@ -152,7 +152,7 @@ describe Chef::Provider::Deploy do it "runs the new resource collection in the runner during a callback" do @runner.should_receive(:converge) - callback_code = lambda { :noop } + callback_code = Proc.new { :noop } @provider.callback(:whatevs, callback_code) end @@ -350,9 +350,11 @@ describe Chef::Provider::Deploy do context "using inline recipes for callbacks" do it "runs an inline recipe with the provided block for :callback_name == {:recipe => &block} " do - recipe_code = lambda {:noop} - @provider.should_receive(:instance_eval).with(&recipe_code) + snitch = nil + recipe_code = Proc.new {snitch = 42} + #@provider.should_receive(:instance_eval).with(&recipe_code) @provider.callback(:whateverz, recipe_code) + snitch.should == 42 end it "loads a recipe file from the specified path and from_file evals it" do @@ -364,7 +366,7 @@ describe Chef::Provider::Deploy do it "instance_evals a block/proc for restart command" do snitch = nil - restart_cmd = lambda {snitch = 42} + restart_cmd = Proc.new {snitch = 42} @resource.restart(&restart_cmd) @provider.restart snitch.should == 42 @@ -393,7 +395,7 @@ describe Chef::Provider::Deploy do snitch = nil @resource.user("tehCat") - callback_code = lambda do + callback_code = Proc.new do snitch = 42 temp_collection = self.instance_variable_get(:@collection) run("tehMice") diff --git a/chef/spec/unit/provider/file_spec.rb b/chef/spec/unit/provider/file_spec.rb index 4208a15890..c73eb78ba4 100644 --- a/chef/spec/unit/provider/file_spec.rb +++ b/chef/spec/unit/provider/file_spec.rb @@ -23,7 +23,7 @@ require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_hel describe Chef::Provider::File do before(:each) do @resource = Chef::Resource::File.new("seattle") - @resource.path(File.join(File.dirname(__FILE__), "..", "..", "data", "templates", "seattle.txt")) + @resource.path(File.expand_path(File.join(CHEF_SPEC_DATA, "templates", "seattle.txt"))) @node = Chef::Node.new @node.name "latte" @provider = Chef::Provider::File.new(@node, @resource) @@ -54,7 +54,7 @@ describe Chef::Provider::File do it "should load a mostly blank current resource if the file specified in new_resource doesn't exist/isn't readable" do resource = Chef::Resource::File.new("seattle") - resource.path(File.join(File.dirname(__FILE__), "..", "..", "data", "templates", "woot.txt")) + resource.path(File.expand_path(File.join(CHEF_SPEC_DATA, "templates", "woot.txt"))) node = Chef::Node.new node.name "latte" provider = Chef::Provider::File.new(node, resource) @@ -68,7 +68,7 @@ describe Chef::Provider::File do end it "should not backup symbolic links on delete" do - path = File.join(File.dirname(__FILE__), "..", "..", "data", "detroit.txt") + path = File.expand_path(File.join(CHEF_SPEC_DATA, "detroit.txt")) ::File.open(path, "w") do |file| file.write("Detroit's not so nice, so you should come to Seattle instead and buy me a beer instead.") end @@ -331,9 +331,8 @@ describe Chef::Provider::File do FileUtils.stub!(:mkdir_p).and_return(true) FileUtils.stub!(:rm).and_return(true) File.stub!(:exist?).and_return(true) - time_becomes_a_loop = mock(Time, :strftime => "wakawaka", :null_object => true, :to_i => 23) - Time.stub!(:now).and_return(time_becomes_a_loop) - FileUtils.should_receive(:cp).with("/tmp/s-20080705111233", "/some_prefix/tmp/s-20080705111233.chef-wakawaka", {:preserve => true}).and_return(true) + Time.stub!(:now).and_return(Time.at(1272147455).getgm) + FileUtils.should_receive(:cp).with("/tmp/s-20080705111233", "/some_prefix/tmp/s-20080705111233.chef-20100424221735", {:preserve => true}).and_return(true) @provider.backup end @@ -342,7 +341,7 @@ end describe Chef::Provider::File, "action_create_if_missing" do before(:each) do @resource = Chef::Resource::File.new("seattle") - @resource.path(File.join(File.dirname(__FILE__), "..", "..", "data", "templates", "seattle.txt")) + @resource.path(File.expand_path(File.join(CHEF_SPEC_DATA, "templates", "seattle.txt"))) @node = Chef::Node.new @node.name "latte" @provider = Chef::Provider::File.new(@node, @resource) diff --git a/chef/spec/unit/provider/group/dscl_spec.rb b/chef/spec/unit/provider/group/dscl_spec.rb index 15ca7ed194..6861c9f783 100644 --- a/chef/spec/unit/provider/group/dscl_spec.rb +++ b/chef/spec/unit/provider/group/dscl_spec.rb @@ -250,12 +250,12 @@ describe Chef::Provider::Group::Dscl, "set_members" do end it "should run safe_dscl with create /Groups/group GroupMembers to clear the Group's GUID list" do - @provider.should_receive(:safe_dscl).with("create /Groups/aj GroupMembers").and_return(true) + @provider.should_receive(:safe_dscl).with("create /Groups/aj GroupMembers ''").and_return(true) @provider.set_members end it "should run safe_dscl with create /Groups/group GroupMembership to clear the Group's UID list" do - @provider.should_receive(:safe_dscl).with("create /Groups/aj GroupMembership").and_return(true) + @provider.should_receive(:safe_dscl).with("create /Groups/aj GroupMembership ''").and_return(true) @provider.set_members end end @@ -276,6 +276,17 @@ describe Chef::Provider::Group::Dscl, "set_members" do @provider.set_members end end + + describe "with no members in the new resource" do + before do + @new_resource.stub!(:members).and_return([]) + end + + it "should not call safe_dscl" do + @provider.should_not_receive(:safe_dscl) + @provider.set_members + end + end end describe Chef::Provider::Group::Dscl, "load_current_resource" do diff --git a/chef/spec/unit/provider/mount/mount_spec.rb b/chef/spec/unit/provider/mount/mount_spec.rb index 1f6be35fe3..e1550002cd 100644 --- a/chef/spec/unit/provider/mount/mount_spec.rb +++ b/chef/spec/unit/provider/mount/mount_spec.rb @@ -6,9 +6,9 @@ # 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. @@ -21,7 +21,7 @@ require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "sp describe Chef::Provider::Mount::Mount, "load_current_resource" do before(:each) do @node = mock("Chef::Node", :null_object => true) - @new_resource = mock("Chef::Resource::Mount", + @new_resource = mock("Chef::Resource::Mount", :null_object => true, :device => "/dev/sdz1", :device_type => :device, @@ -31,8 +31,8 @@ describe Chef::Provider::Mount::Mount, "load_current_resource" do :mounted => false ) @new_resource.stub!(:supports).and_return({:remount => false}) - - @current_resource = mock("Chef::Resource::Mount", + + @current_resource = mock("Chef::Resource::Mount", :null_object => true, :device => "/dev/sdz1", :device_type => :device, @@ -41,10 +41,10 @@ describe Chef::Provider::Mount::Mount, "load_current_resource" do :fstype => "ext3", :mounted => false ) - + @provider = Chef::Provider::Mount::Mount.new(@node, @new_resource) Chef::Resource::Mount.stub!(:new).and_return(@current_resource) - ::File.stub!(:read).with("/etc/fstab").and_return "\n" + ::File.stub!(:foreach).with("/etc/fstab") ::File.stub!(:exists?).with("/dev/sdz1").and_return true ::File.stub!(:exists?).with("/tmp/foo").and_return true @@ -55,22 +55,22 @@ describe Chef::Provider::Mount::Mount, "load_current_resource" do @stderr = mock("STDERR", :null_object => true) @pid = mock("PID", :null_object => true) end - + it "should create a current resource with the name of the new resource" do Chef::Resource::Mount.should_receive(:new).and_return(@current_resource) @provider.load_current_resource() end - + it "should set the current resources mount point to the new resources mount point" do @current_resource.should_receive(:mount_point).with(@new_resource.mount_point) @provider.load_current_resource() end - + it "should set the current resources device to the new resources device" do @current_resource.should_receive(:device).with(@new_resource.device) @provider.load_current_resource() end - + it "should raise an error if the mount device does not exist" do ::File.stub!(:exists?).with("/dev/sdz1").and_return false lambda { @provider.load_current_resource() }.should raise_error(Chef::Exceptions::Mount) @@ -88,10 +88,10 @@ describe Chef::Provider::Mount::Mount, "load_current_resource" do @current_resource.should_receive(:mounted).with(true) @provider.load_current_resource() end - + it "should set mounted true if the symlink target of the device is found in the mounts list" do target = "/dev/mapper/target" - + ::File.stub!(:symlink?).with("#{@new_resource.device}").and_return(true) ::File.stub!(:readlink).with("#{@new_resource.device}").and_return(target) @@ -100,86 +100,86 @@ describe Chef::Provider::Mount::Mount, "load_current_resource" do @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(0) @current_resource.should_receive(:mounted).with(true) @provider.load_current_resource() - + end it "should set mounted true if the mount point is found last in the mounts list" do mount = "/dev/sdy1 on #{@new_resource.mount_point} type ext3 (rw)\n" - mount << "#{@new_resource.device} on #{@new_resource.mount_point} type ext3 (rw)\n" - + mount << "#{@new_resource.device} on #{@new_resource.mount_point} type ext3 (rw)\n" + y = @stdout.stub!(:each) - mount.each {|l| y.and_yield(l)} - + mount.each_line {|l| y.and_yield(l)} + @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(0) @current_resource.should_receive(:mounted).with(true) @provider.load_current_resource() end - + it "should set mounted false if the mount point is not last in the mounts list" do mount = "#{@new_resource.device} on #{@new_resource.mount_point} type ext3 (rw)\n" mount << "/dev/sdy1 on #{@new_resource.mount_point} type ext3 (rw)\n" y = @stdout.stub!(:each) - mount.each {|l| y.and_yield(l)} + mount.each_line {|l| y.and_yield(l)} @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(0) @current_resource.should_receive(:mounted).with(false) @provider.load_current_resource() end - + it "mounted should be false if the mount point is not found in the mounts list" do @stdout.stub!(:each).and_yield("/dev/sdy1 on #{@new_resource.mount_point} type ext3 (rw)\n") @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(0) @current_resource.should_receive(:mounted).with(false) @provider.load_current_resource() end - + it "should set enabled to true if the mount point is last in fstab" do fstab = "/dev/sdy1 #{@new_resource.mount_point} ext3 defaults 1 2\n" fstab << "#{@new_resource.device} #{@new_resource.mount_point} ext3 defaults 1 2\n" - - ::File.stub!(:read).with("/etc/fstab").and_return fstab - + + ::File.stub!(:foreach).with("/etc/fstab").and_yield fstab + @current_resource.should_receive(:enabled).with(true) @provider.load_current_resource end - + it "should set enabled to true if the symlink target is in fstab" do target = "/dev/mapper/target" - + ::File.stub!(:symlink?).with("#{@new_resource.device}").and_return(true) ::File.stub!(:readlink).with("#{@new_resource.device}").and_return(target) fstab = "#{@new_resource.device} #{@new_resource.mount_point} ext3 defaults 1 2\n" - - ::File.stub!(:read).with("/etc/fstab").and_return fstab - + + ::File.stub!(:foreach).with("/etc/fstab").and_yield fstab + @current_resource.should_receive(:enabled).with(true) @provider.load_current_resource end - + it "should set enabled to false if the mount point is not in fstab" do fstab = "/dev/sdy1 #{@new_resource.mount_point} ext3 defaults 1 2\n" - ::File.stub!(:read).with("/etc/fstab").and_return fstab + ::File.stub!(:foreach).with("/etc/fstab").and_yield fstab @current_resource.should_receive(:enabled).with(false) @provider.load_current_resource - + end it "should ignore commented lines in fstab " do fstab = "\# #{@new_resource.device} #{@new_resource.mount_point} ext3 defaults 1 2\n" - ::File.stub!(:read).with("/etc/fstab").and_return fstab + ::File.stub!(:foreach).with("/etc/fstab").and_yield fstab @current_resource.should_receive(:enabled).with(false) @provider.load_current_resource end it "should set enabled to false if the mount point is not last in fstab" do - fstab = "#{@new_resource.device} #{@new_resource.mount_point} ext3 defaults 1 2\n" - fstab << "/dev/sdy1 #{@new_resource.mount_point} ext3 defaults 1 2\n" - ::File.stub!(:read).with("/etc/fstab").and_return fstab - + line_1 = "#{@new_resource.device} #{@new_resource.mount_point} ext3 defaults 1 2\n" + line_2 = "/dev/sdy1 #{@new_resource.mount_point} ext3 defaults 1 2\n" + ::File.stub!(:foreach).with("/etc/fstab").and_yield(line_1).and_yield(line_2) + @current_resource.should_receive(:enabled).with(false) @provider.load_current_resource end @@ -188,7 +188,7 @@ end describe Chef::Provider::Mount::Mount, "mount_fs" do before(:each) do @node = mock("Chef::Node", :null_object => true) - @new_resource = mock("Chef::Resource::Mount", + @new_resource = mock("Chef::Resource::Mount", :null_object => true, :device => "/dev/sdz1", :device_type => :device, @@ -199,8 +199,8 @@ describe Chef::Provider::Mount::Mount, "mount_fs" do :mounted => false ) @new_resource.stub!(:supports).and_return({:remount => false}) - - @current_resource = mock("Chef::Resource::Mount", + + @current_resource = mock("Chef::Resource::Mount", :null_object => true, :device => "/dev/sdz1", :device_type => :device, @@ -210,11 +210,11 @@ describe Chef::Provider::Mount::Mount, "mount_fs" do :device_type => :device, :mounted => false ) - + @provider = Chef::Provider::Mount::Mount.new(@node, @new_resource) Chef::Resource::Mount.stub!(:new).and_return(@current_resource) @provider.current_resource = @current_resource - + @status = mock("Status", :exitstatus => 0) @provider.stub!(:popen4).and_return(@status) @stdin = mock("STDIN", :null_object => true) @@ -222,14 +222,14 @@ describe Chef::Provider::Mount::Mount, "mount_fs" do @stderr = mock("STDERR", :null_object => true) @pid = mock("PID", :null_object => true) end - + it "should mount the filesystem if it is not mounted" do @stdout.stub!(:each).and_yield("#{@new_resource.device} on #{@new_resource.mount_point}") @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(0) @provider.should_receive(:run_command).with({:command => "mount -t #{@new_resource.fstype} #{@new_resource.device} #{@new_resource.mount_point}"}) @provider.mount_fs() end - + it "should mount the filesystem with options if options were passed" do options = "rw,noexec,noauto" @stdout.stub!(:each).and_yield("#{@new_resource.mount_point} on #{@new_resource.mount_point}") @@ -238,19 +238,19 @@ describe Chef::Provider::Mount::Mount, "mount_fs" do @provider.should_receive(:run_command).with({:command => "mount -t #{@new_resource.fstype} -o #{options} #{@new_resource.device} #{@new_resource.mount_point}"}) @provider.mount_fs() end - + it "should not mount the filesystem if it is mounted" do @current_resource.stub!(:mounted).and_return(true) @provider.should_not_receive(:run_command).with({:command => "mount -t #{@new_resource.fstype} #{@new_resource.device} #{@new_resource.mount_point}"}) @provider.mount_fs() end - + end describe Chef::Provider::Mount::Mount, "umount_fs" do before(:each) do @node = mock("Chef::Node", :null_object => true) - @new_resource = mock("Chef::Resource::Mount", + @new_resource = mock("Chef::Resource::Mount", :null_object => true, :device => "/dev/sdz1", :device_type => :device, @@ -260,8 +260,8 @@ describe Chef::Provider::Mount::Mount, "umount_fs" do :mounted => true ) @new_resource.stub!(:supports).and_return({:remount => false}) - - @current_resource = mock("Chef::Resource::Mount", + + @current_resource = mock("Chef::Resource::Mount", :null_object => true, :device => "/dev/sdz1", :device_type => :device, @@ -270,11 +270,11 @@ describe Chef::Provider::Mount::Mount, "umount_fs" do :fstype => "ext3", :mounted => true ) - + @provider = Chef::Provider::Mount::Mount.new(@node, @new_resource) Chef::Resource::Mount.stub!(:new).and_return(@current_resource) @provider.current_resource = @current_resource - + @status = mock("Status", :exitstatus => 0) @provider.stub!(:popen4).and_return(@status) @stdin = mock("STDIN", :null_object => true) @@ -282,7 +282,7 @@ describe Chef::Provider::Mount::Mount, "umount_fs" do @stderr = mock("STDERR", :null_object => true) @pid = mock("PID", :null_object => true) end - + it "should umount the filesystem if it is mounted" do @stdout.stub!(:each).and_yield("#{@new_resource.device} on #{@new_resource.mount_point}") @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(0) @@ -300,7 +300,7 @@ end describe Chef::Provider::Mount::Mount, "remount_fs" do before(:each) do @node = mock("Chef::Node", :null_object => true) - @new_resource = mock("Chef::Resource::Mount", + @new_resource = mock("Chef::Resource::Mount", :null_object => true, :device => "/dev/sdz1", :device_type => :device, @@ -310,8 +310,8 @@ describe Chef::Provider::Mount::Mount, "remount_fs" do :mounted => true ) @new_resource.stub!(:supports).and_return({:remount => false}) - - @current_resource = mock("Chef::Resource::Mount", + + @current_resource = mock("Chef::Resource::Mount", :null_object => true, :device => "/dev/sdz1", :device_type => :device, @@ -320,11 +320,11 @@ describe Chef::Provider::Mount::Mount, "remount_fs" do :fstype => "ext3", :mounted => true ) - + @provider = Chef::Provider::Mount::Mount.new(@node, @new_resource) Chef::Resource::Mount.stub!(:new).and_return(@current_resource) @provider.current_resource = @current_resource - + @status = mock("Status", :exitstatus => 0) @provider.stub!(:popen4).and_return(@status) @stdin = mock("STDIN", :null_object => true) @@ -347,7 +347,7 @@ describe Chef::Provider::Mount::Mount, "remount_fs" do @provider.should_receive(:mount_fs) @provider.remount_fs() end - + it "should not try to remount at all if mounted is false" do @current_resource.stub!(:mounted).and_return(false) @provider.should_not_receive(:run_command).with({:command => "mount -o remount #{@new_resource.mount_point}"}) @@ -361,7 +361,7 @@ end describe Chef::Provider::Mount::Mount, "enable_fs" do before(:each) do @node = mock("Chef::Node", :null_object => true) - @new_resource = mock("Chef::Resource::Mount", + @new_resource = mock("Chef::Resource::Mount", :null_object => true, :device => "/dev/sdz1", :device_type => :device, @@ -374,36 +374,56 @@ describe Chef::Provider::Mount::Mount, "enable_fs" do :pass => 2 ) @new_resource.stub!(:supports).and_return({:remount => false}) - - @current_resource = mock("Chef::Resource::Mount", + + @current_resource = mock("Chef::Resource::Mount", :null_object => true, :device => "/dev/sdz1", :device_type => :device, :name => "/tmp/foo", :mount_point => "/tmp/foo", :fstype => "ext3", - :mounted => false + :mounted => false, + :options => ["defaults"], + :dump => 0, + :pass => 2 ) - + @provider = Chef::Provider::Mount::Mount.new(@node, @new_resource) Chef::Resource::Mount.stub!(:new).and_return(@current_resource) @provider.current_resource = @current_resource @fstab = mock("File", :null_object => true) end - + it "should enable if enabled isn't true" do @current_resource.stub!(:enabled).and_return(false) - + ::File.stub!(:open).with("/etc/fstab", "a").and_yield(@fstab) @fstab.should_receive(:puts).with(/^#{@new_resource.device}\s+#{@new_resource.mount_point}\s+#{@new_resource.fstype}\s+defaults\s+#{@new_resource.dump}\s+#{@new_resource.pass}\s*$/) - + @provider.enable_fs end - - it "should not enabled if enabled is true" do + + it "should not enable if enabled is true and resources match" do @current_resource.stub!(:enabled).and_return(true) + @current_resource.stub!(:fstype).and_return("ext3") + @current_resource.stub!(:options).and_return(["defaults"]) + @current_resource.stub!(:dump).and_return(0) + @current_resource.stub!(:pass).and_return(2) ::File.should_not_receive(:open).with("/etc/fstab", "a").and_yield(@fstab) + + @provider.enable_fs + end + + it "should enable if enabled is true and resources do not match" do + @current_resource.stub!(:enabled).and_return(true) + @current_resource.stub!(:fstype).and_return("auto") + @current_resource.stub!(:options).and_return(["defaults"]) + @current_resource.stub!(:dump).and_return(0) + @current_resource.stub!(:pass).and_return(2) + ::File.stub(:readlines).and_return([]) + ::File.should_receive(:open).once.with("/etc/fstab", "w").and_yield(@fstab) + ::File.should_receive(:open).once.with("/etc/fstab", "a").and_yield(@fstab) @provider.enable_fs end @@ -412,7 +432,7 @@ end describe Chef::Provider::Mount::Mount, "disable_fs" do before(:each) do @node = mock("Chef::Node", :null_object => true) - @new_resource = mock("Chef::Resource::Mount", + @new_resource = mock("Chef::Resource::Mount", :null_object => true, :device => "/dev/sdz1", :device_type => :device, @@ -425,8 +445,8 @@ describe Chef::Provider::Mount::Mount, "disable_fs" do :pass => 2 ) @new_resource.stub!(:supports).and_return({:remount => false}) - - @current_resource = mock("Chef::Resource::Mount", + + @current_resource = mock("Chef::Resource::Mount", :null_object => true, :device => "/dev/sdz1", :device_type => :device, @@ -435,7 +455,7 @@ describe Chef::Provider::Mount::Mount, "disable_fs" do :fstype => "ext3", :mounted => false ) - + @provider = Chef::Provider::Mount::Mount.new(@node, @new_resource) Chef::Resource::Mount.stub!(:new).and_return(@current_resource) @provider.current_resource = @current_resource @@ -443,64 +463,64 @@ describe Chef::Provider::Mount::Mount, "disable_fs" do @fstab = mock("File", :null_object => true) end - it "should disable if enabled is true" do + it "should disable if enabled is true" do @current_resource.stub!(:enabled).and_return(true) - + fstab = ["/dev/sdy1 #{@new_resource.mount_point} ext3 defaults 1 2\n"] fstab << "#{@new_resource.device} #{@new_resource.mount_point} ext3 defaults 1 2\n" - + ::File.stub!(:readlines).with("/etc/fstab").and_return(fstab) ::File.stub!(:open).with("/etc/fstab", "w").and_yield(@fstab) - + @fstab.should_receive(:puts).with(fstab[0]).once.ordered @fstab.should_not_receive(:puts).with(fstab[1]) - + @provider.disable_fs end - - it "should disable if enabled is true and ignore commented lines" do + + it "should disable if enabled is true and ignore commented lines" do @current_resource.stub!(:enabled).and_return(true) - + fstab = ["/dev/sdy1 #{@new_resource.mount_point} ext3 defaults 1 2\n"] fstab << "#{@new_resource.device} #{@new_resource.mount_point} ext3 defaults 1 2\n" fstab << "\##{@new_resource.device} #{@new_resource.mount_point} ext3 defaults 1 2\n" - - + + ::File.stub!(:readlines).with("/etc/fstab").and_return(fstab) ::File.stub!(:open).with("/etc/fstab", "w").and_yield(@fstab) - + @fstab.should_receive(:puts).with(fstab[0]).once.ordered @fstab.should_receive(:puts).with(fstab[2]).once.ordered @fstab.should_not_receive(:puts).with(fstab[1]) - + @provider.disable_fs end - it "should disable only the last entry if enabled is true" do + it "should disable only the last entry if enabled is true" do @current_resource.stub!(:enabled).and_return(true) fstab = ["#{@new_resource.device} #{@new_resource.mount_point} ext3 defaults 1 2\n"] fstab << "/dev/sdy1 #{@new_resource.mount_point} ext3 defaults 1 2\n" fstab << "#{@new_resource.device} #{@new_resource.mount_point} ext3 defaults 1 2\n" - - + + ::File.stub!(:readlines).with("/etc/fstab").and_return(fstab) ::File.stub!(:open).with("/etc/fstab", "w").and_yield(@fstab) - + @fstab.should_receive(:puts).with(fstab[0]).once.ordered @fstab.should_receive(:puts).with(fstab[1]).once.ordered - + @fstab.should_not_receive(:puts).with(fstab[2]) - + @provider.disable_fs end - + it "should not disable if enabled is false" do @current_resource.stub!(:enabled).and_return(false) - + ::File.stub!(:readlines).with("/etc/fstab").and_return([]) ::File.should_not_receive(:open).and_yield(@fstab) - + @provider.disable_fs end end diff --git a/chef/spec/unit/provider/package/freebsd_spec.rb b/chef/spec/unit/provider/package/freebsd_spec.rb index b75a34eba1..50abf405ac 100644 --- a/chef/spec/unit/provider/package/freebsd_spec.rb +++ b/chef/spec/unit/provider/package/freebsd_spec.rb @@ -7,9 +7,9 @@ # 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. @@ -21,42 +21,31 @@ require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "sp describe Chef::Provider::Package::Freebsd, "load_current_resource" do before(:each) do - @node = mock("Chef::Node", :null_object => true) - @new_resource = mock("Chef::Resource::Package", - :null_object => true, - :name => "zsh", - :package_name => "zsh", - :version => nil - ) - @current_resource = mock("Chef::Resource::Package", - :null_object => true, - :name => "zsh", - :package_name => "zsh", - :version => nil - ) - - @provider = Chef::Provider::Package::Freebsd.new(@node, @new_resource) - Chef::Resource::Package.stub!(:new).and_return(@current_resource) - - @provider.should_receive(:ports_candidate_version).and_return("4.3.6") + @node = Chef::Node.new + @new_resource = Chef::Resource::Package.new("zsh") + @current_resource = Chef::Resource::Package.new("zsh") + + @provider = Chef::Provider::Package::Freebsd.new(@node, @new_resource) + @provider.current_resource = @current_resource + + @provider.stub!(:ports_candidate_version).and_return("4.3.6") end it "should create a current resource with the name of the new_resource" do - Chef::Resource::Package.should_receive(:new).and_return(@current_resource) - @provider.should_receive(:current_installed_version).and_return(nil) - @provider.load_current_resource + current_resource = Chef::Provider::Package::Freebsd.new(@node, @new_resource).current_resource + current_resource.name.should == "zsh" end it "should return a version if the package is installed" do @provider.should_receive(:current_installed_version).and_return("4.3.6_7") - @current_resource.should_receive(:version).with("4.3.6_7").and_return(true) @provider.load_current_resource + @current_resource.version.should == "4.3.6_7" end it "should return nil if the package is not installed" do @provider.should_receive(:current_installed_version).and_return(nil) - @current_resource.should_receive(:version).with(nil).and_return(true) @provider.load_current_resource + @current_resource.version.should be_nil end it "should return a candidate version if it exists" do @@ -68,14 +57,9 @@ end describe Chef::Provider::Package::Freebsd, "system call wrappers" do before(:each) do - @new_resource = mock("Chef::Resource::Package", - :null_object => true, - :name => "zsh", - :package_name => "zsh", - :version => nil - ) + @new_resource = Chef::Resource::Package.new("zsh") - @provider = Chef::Provider::Package::Freebsd.new(@node, @new_resource) + @provider = Chef::Provider::Package::Freebsd.new(@node, @new_resource) @status = mock("Status", :exitstatus => 0) @stdin = mock("STDIN", :null_object => true) @@ -95,7 +79,7 @@ describe Chef::Provider::Package::Freebsd, "system call wrappers" do @provider.stub!(:package_name).and_return("zsh") @provider.current_installed_version.should be_nil end - + it "should return the port path for a valid port name" do @provider.should_receive(:popen4).with("whereis -s zsh").and_yield(@pid, @stdin, ["zsh: /usr/ports/shells/zsh"], @stderr).and_return(@status) @provider.stub!(:port_name).and_return("zsh") @@ -109,7 +93,7 @@ describe Chef::Provider::Package::Freebsd, "system call wrappers" do @stdout.should_receive(:readline).and_return("4.3.6\n") @provider.ports_candidate_version.should == "4.3.6" end - + it "should figure out the package name" do @provider.should_receive(:ports_makefile_variable_value).with("PKGNAME").and_return("zsh-4.3.6_7") @provider.package_name.should == "zsh" @@ -119,18 +103,8 @@ end describe Chef::Provider::Package::Freebsd, "install_package" do before(:each) do @node = mock("Chef::Node", :null_object => true) - @new_resource = mock("Chef::Resource::Package", - :null_object => true, - :name => "zsh", - :package_name => "zsh", - :version => nil - ) - @current_resource = mock("Chef::Resource::Package", - :null_object => true, - :name => "zsh", - :package_name => "zsh", - :version => nil - ) + @new_resource = Chef::Resource::Package.new("zsh") + @current_resource = Chef::Resource::Package.new("zsh") @provider = Chef::Provider::Package::Freebsd.new(@node, @new_resource) @provider.current_resource = @current_resource @provider.stub!(:package_name).and_return("zsh") @@ -153,49 +127,41 @@ describe Chef::Provider::Package::Freebsd, "install_package" do end describe Chef::Provider::Package::Freebsd, "port path" do + before do + @node = Chef::Node.new + @new_resource = Chef::Resource::Package.new("zsh") + @new_resource.cookbook_name = "adventureclub" + @provider = Chef::Provider::Package::Freebsd.new(@node, @new_resource) + end + it "should figure out the port path from the package_name using whereis" do - @new_resource = mock( "Chef::Resource::Package", - :package_name => "zsh", - :cookbook_name => "adventureclub") - @provider = Chef::Provider::Package::Freebsd.new(mock("Chef::Node"), @new_resource) @provider.should_receive(:popen4).with("whereis -s zsh").and_yield(nil, nil, ["zsh: /usr/ports/shells/zsh"], nil) @provider.port_path.should == "/usr/ports/shells/zsh" end - + it "should use the package_name as the port path when it starts with /" do - @new_resource = mock( "Chef::Resource::Package", - :package_name => "/usr/ports/www/wordpress", - :cookbook_name => "adventureclub") - @provider = Chef::Provider::Package::Freebsd.new(mock("Chef::Node"), @new_resource) - @provider.should_not_receive(:popen4) - @provider.port_path.should == "/usr/ports/www/wordpress" + new_resource = Chef::Resource::Package.new("/usr/ports/www/wordpress") + provider = Chef::Provider::Package::Freebsd.new(@node, new_resource) + provider.should_not_receive(:popen4) + provider.port_path.should == "/usr/ports/www/wordpress" end - + it "should use the package_name as a relative path from /usr/ports when it contains / but doesn't start with it" do - @new_resource = mock( "Chef::Resource::Package", - :package_name => "www/wordpress", - :cookbook_name => "xenoparadox") - @provider = Chef::Provider::Package::Freebsd.new(mock("Chef::Node"), @new_resource) - @provider.should_not_receive(:popen4) - @provider.port_path.should == "/usr/ports/www/wordpress" - end + # @new_resource = mock( "Chef::Resource::Package", + # :package_name => "www/wordpress", + # :cookbook_name => "xenoparadox") + new_resource = Chef::Resource::Package.new("www/wordpress") + provider = Chef::Provider::Package::Freebsd.new(@node, new_resource) + provider.should_not_receive(:popen4) + provider.port_path.should == "/usr/ports/www/wordpress" + end end describe Chef::Provider::Package::Freebsd, "ruby-iconv (package with a dash in the name)" do before(:each) do @node = mock("Chef::Node", :null_object => true) - @new_resource = mock("Chef::Resource::Package", - :null_object => true, - :name => "ruby-iconv", - :package_name => "ruby-iconv", - :version => nil - ) - @current_resource = mock("Chef::Resource::Package", - :null_object => true, - :name => "ruby-iconv", - :package_name => "ruby-iconv", - :version => nil - ) + @new_resource = Chef::Resource::Package.new("ruby-iconv") + @current_resource = Chef::Resource::Package.new("ruby-iconv") @provider = Chef::Provider::Package::Freebsd.new(@node, @new_resource) @provider.current_resource = @current_resource @provider.stub!(:port_path).and_return("/usr/ports/converters/ruby-iconv") @@ -218,18 +184,10 @@ end describe Chef::Provider::Package::Freebsd, "remove_package" do before(:each) do @node = mock("Chef::Node", :null_object => true) - @new_resource = mock("Chef::Resource::Package", - :null_object => true, - :name => "zsh", - :package_name => "zsh", - :version => "4.3.6_7" - ) - @current_resource = mock("Chef::Resource::Package", - :null_object => true, - :name => "zsh", - :package_name => "zsh", - :version => "4.3.6_7" - ) + @new_resource = Chef::Resource::Package.new("zsh") + @new_resource.version "4.3.6_7" + @current_resource = Chef::Resource::Package.new("zsh") + @current_resource.version "4.3.6_7" @provider = Chef::Provider::Package::Freebsd.new(@node, @new_resource) @provider.current_resource = @current_resource @provider.stub!(:package_name).and_return("zsh") @@ -263,18 +221,8 @@ end describe Chef::Provider::Package::Freebsd, "install_package latest link fixes" do it "should install the perl binary package with the correct name" do @node = mock("Chef::Node", :null_object => true) - @new_resource = mock("Chef::Resource::Package", - :null_object => true, - :name => "perl5.8", - :package_name => "perl5.8", - :version => nil - ) - @current_resource = mock("Chef::Resource::Package", - :null_object => true, - :name => "perl5.8", - :package_name => "perl5.8", - :version => nil - ) + @new_resource = Chef::Resource::Package.new("perl5.8") + @current_resource = Chef::Resource::Package.new("perl5.8") @provider = Chef::Provider::Package::Freebsd.new(@node, @new_resource) @provider.current_resource = @current_resource @provider.stub!(:package_name).and_return("perl") @@ -288,18 +236,8 @@ describe Chef::Provider::Package::Freebsd, "install_package latest link fixes" d it "should install the mysql50-server binary package with the correct name" do @node = mock("Chef::Node", :null_object => true) - @new_resource = mock("Chef::Resource::Package", - :null_object => true, - :name => "mysql50-server", - :package_name => "mysql50-server", - :version => nil - ) - @current_resource = mock("Chef::Resource::Package", - :null_object => true, - :name => "mysql50-server", - :package_name => "mysql50-server", - :version => nil - ) + @new_resource = Chef::Resource::Package.new("mysql50-server") + @current_resource = Chef::Resource::Package.new("mysql50-server") @provider = Chef::Provider::Package::Freebsd.new(@node, @new_resource) @provider.current_resource = @current_resource @provider.stub!(:package_name).and_return("mysql-server") diff --git a/chef/spec/unit/provider/package/portage_spec.rb b/chef/spec/unit/provider/package/portage_spec.rb index 620c24fc91..1abb502919 100644 --- a/chef/spec/unit/provider/package/portage_spec.rb +++ b/chef/spec/unit/provider/package/portage_spec.rb @@ -43,13 +43,13 @@ describe Chef::Provider::Package::Portage, "load_current_resource" do end it "should create a current resource with the name of new_resource" do - ::Dir.stub!(:entries).and_return("git-1.0.0") + ::Dir.stub!(:entries).and_return(["git-1.0.0"]) Chef::Resource::Package.should_receive(:new).and_return(@current_resource) @provider.load_current_resource end it "should set the current resource package name to the new resource package name" do - ::Dir.stub!(:entries).and_return("git-1.0.0") + ::Dir.stub!(:entries).and_return(["git-1.0.0"]) @current_resource.should_receive(:package_name).with(@new_resource.package_name) @provider.load_current_resource end @@ -61,13 +61,13 @@ describe Chef::Provider::Package::Portage, "load_current_resource" do end it "should return a current resource with the correct version if the package is found with revision" do - ::Dir.stub!(:entries).and_return("git-1.0.0-r1") + ::Dir.stub!(:entries).and_return(["git-1.0.0-r1"]) @current_resource.should_receive(:version).with("1.0.0-r1") @provider.load_current_resource end it "should return a current resource with a nil version if the package is not found" do - ::Dir.stub!(:entries).and_return("notgit-1.0.0") + ::Dir.stub!(:entries).and_return(["notgit-1.0.0"]) @current_resource.should_receive(:version).with(nil) @provider.load_current_resource end diff --git a/chef/spec/unit/provider/package/rubygems_spec.rb b/chef/spec/unit/provider/package/rubygems_spec.rb index f3eed8c1aa..61e1affc63 100644 --- a/chef/spec/unit/provider/package/rubygems_spec.rb +++ b/chef/spec/unit/provider/package/rubygems_spec.rb @@ -1,15 +1,15 @@ # # Author:: David Balatero (dbalatero@gmail.com) # -# Copyright:: Copyright (c) 2009 David Balatero +# Copyright:: Copyright (c) 2009 David Balatero # 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. @@ -19,52 +19,58 @@ require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "spec_helper")) -describe Chef::Provider::Package::Rubygems, "gem_binary_path" do +describe Chef::Provider::Package::Rubygems do before(:each) do - @node = mock("Chef::Node", :null_object => true) - @new_resource = mock("Chef::Resource::Package", - :null_object => true, - :name => "rspec", - :version => "1.2.2", - :package_name => "rspec", - :updated => nil, - :gem_binary => nil - ) + @node = Chef::Node.new + @new_resource = Chef::Resource::GemPackage.new("nokogiri") + @new_resource.version "1.4.1" @provider = Chef::Provider::Package::Rubygems.new(@node, @new_resource) end - it "should return a relative path to gem if no gem_binary is given" do - @provider.gem_binary_path.should eql("gem") - end + describe "when selecting the gem binary to use" do + it "should return a relative path to gem if no gem_binary is given" do + @provider.gem_binary_path.should == "gem" + end - it "should return a specific path to gem if a gem_binary is given" do - @new_resource.should_receive(:gem_binary).and_return("/opt/local/bin/custom/ruby") - @provider.gem_binary_path.should eql("/opt/local/bin/custom/ruby") + it "should return a specific path to gem if a gem_binary is given" do + @new_resource.gem_binary "/opt/local/bin/custom/ruby" + @provider.gem_binary_path.should == "/opt/local/bin/custom/ruby" + end end -end -describe Chef::Provider::Package::Rubygems, "install_package" do - before(:each) do - @node = mock("Chef::Node", :null_object => true) - @new_resource = Chef::Resource::GemPackage.new("rspec") - @new_resource.version "1.2.2" - @provider = Chef::Provider::Package::Rubygems.new(@node, @new_resource) + describe "loading the current state" do + it "determines the installed versions of gems" do + gem_list = "nokogiri (2.3.5, 2.2.2, 1.2.6)" + @provider.gem_list_parse(gem_list).should == %w{2.3.5 2.2.2 1.2.6} + end end - it "should run gem install with the package name and version" do - @provider.should_receive(:run_command).with({ - :command => "gem install rspec -q --no-rdoc --no-ri -v \"1.2.2\"", - :environment => { - "LC_ALL" => nil - } - }) - @provider.install_package("rspec", "1.2.2") + describe "determining the candidate version" do + it "parses the available versions as reported by rubygems 1.3.6 and lower" do + gem_list = "nokogiri (1.4.1)\nnokogiri-happymapper (0.3.3)" + @provider.gem_list_parse(gem_list).should == ['1.4.1'] + end + + it "parses the available versions as reported by rubygems 1.3.7 and newer" do + gem_list = "nokogiri (1.4.1 ruby java x86-mingw32 x86-mswin32)\nnokogiri-happymapper (0.3.3)\n" + @provider.gem_list_parse(gem_list).should == ['1.4.1'] + end + end - - it "installs gems with arbitrary options set by resource's options" do - @new_resource.options "-i /arbitrary/install/dir" - @provider.should_receive(:run_command_with_systems_locale). - with(:command => "gem install rspec -q --no-rdoc --no-ri -v \"1.2.2\" -i /arbitrary/install/dir") - @provider.install_package("rspec", "1.2.2") + + describe "when installing a gem" do + it "should run gem install with the package name and version" do + @provider.should_receive(:run_command).with( + :command => "gem install rspec -q --no-rdoc --no-ri -v \"1.2.2\"", + :environment => {"LC_ALL" => nil}) + @provider.install_package("rspec", "1.2.2") + end + + it "installs gems with arbitrary options set by resource's options" do + @new_resource.options "-i /arbitrary/install/dir" + @provider.should_receive(:run_command_with_systems_locale). + with(:command => "gem install rspec -q --no-rdoc --no-ri -v \"1.2.2\" -i /arbitrary/install/dir") + @provider.install_package("rspec", "1.2.2") + end end end diff --git a/chef/spec/unit/provider/package_spec.rb b/chef/spec/unit/provider/package_spec.rb index 477aee6785..a74436b4f7 100644 --- a/chef/spec/unit/provider/package_spec.rb +++ b/chef/spec/unit/provider/package_spec.rb @@ -315,7 +315,7 @@ describe Chef::Provider::Package, "preseed_package" do end it "should raise Chef::Exceptions::UnsupportedAction" do - lambda { @provider.preseed_package(@new_resource.name, @new_resource.version, "response_file") }.should raise_error(Chef::Exceptions::UnsupportedAction) + lambda { @provider.preseed_package(@new_resource.name, @new_resource.version) }.should raise_error(Chef::Exceptions::UnsupportedAction) end end diff --git a/chef/spec/unit/provider/remote_directory_spec.rb b/chef/spec/unit/provider/remote_directory_spec.rb index d88a5ef62a..ee00f7bef5 100644 --- a/chef/spec/unit/provider/remote_directory_spec.rb +++ b/chef/spec/unit/provider/remote_directory_spec.rb @@ -104,7 +104,7 @@ describe Chef::Provider::RemoteDirectory do it "lists the directory contents from the cookbook for chef-solo" do Chef::Config[:solo] = true - @source_path = File.join(File.dirname(__FILE__), "..", "..", "data", "remote_directory_data") + @source_path = File.expand_path(File.join(CHEF_SPEC_DATA, "remote_directory_data")) @resource.source(@source_path) @provider.stub!(:find_preferred_file).and_return(@source_path) @@ -126,5 +126,26 @@ describe Chef::Provider::RemoteDirectory do end + it "removes existing files if purge is true" do + @resource.purge(true) + @provider.stub!(:files_to_transfer).and_return(["fileA", "fileB"]) + @provider.stub!(:fetch_remote_file).and_return + ::Dir.stub!(:[]).with("#{@resource.path}/**/*").and_return(["#{@resource.path}/fileA", "#{@resource.path}/delete_this_file.txt"]) + ::File.should_receive(:directory?) + ::File.should_receive(:delete).with("#{@resource.path}/delete_this_file.txt") + @provider.send(:do_recursive) + end + + it "removes files in subdirectories before files above" do + @resource.purge(true) + @provider.stub!(:files_to_transfer).and_return(["fileA", "fileB"]) + @provider.stub!(:fetch_remote_file).and_return + ::Dir.stub!(:[]).with("#{@resource.path}/**/*").and_return(["#{@resource.path}/fileA", "#{@resource.path}/dir", "#{@resource.path}/dir/f1"]) + ::File.should_receive(:directory?).with("#{@resource.path}/dir/f1").and_return(false) + ::File.should_receive(:directory?).with("#{@resource.path}/dir").and_return(true) + ::File.should_receive(:delete).ordered.with("#{@resource.path}/dir/f1") + ::Dir.should_receive(:rmdir).ordered.with("#{@resource.path}/dir") + @provider.send(:do_recursive) + end end end diff --git a/chef/spec/unit/provider/remote_file_spec.rb b/chef/spec/unit/provider/remote_file_spec.rb index cce78a5e2a..4dbf034b02 100644 --- a/chef/spec/unit/provider/remote_file_spec.rb +++ b/chef/spec/unit/provider/remote_file_spec.rb @@ -6,9 +6,9 @@ # 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. @@ -21,31 +21,42 @@ require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_hel describe Chef::Provider::RemoteFile, "action_create" do before(:each) do @resource = Chef::Resource::RemoteFile.new("seattle") - @resource.path(File.join(File.dirname(__FILE__), "..", "..", "data", "seattle.txt")) + @resource.path(File.join(CHEF_SPEC_DATA, "templates", "seattle.txt")) @resource.source("http://foo") @node = Chef::Node.new @node.name "latte" @provider = Chef::Provider::RemoteFile.new(@node, @resource) @provider.current_resource = @resource.clone end - + it "should call do_remote_file" do @provider.should_receive(:do_remote_file).with(@resource.source, @resource.path) @provider.action_create end + describe "when checking if the file is at the target version" do + it "considers the current file to be at the target version if it exists and matches the user-provided checksum" do + @resource.checksum("0fd012fdc96e96f8f7cf2046522a54aed0ce470224513e45da6bc1a17a4924aa") + @provider.current_resource.checksum("0fd012fdc96e96f8f7cf2046522a54aed0ce470224513e45da6bc1a17a4924aa") + @provider.current_resource_matches_target_checksum?.should be_true + end + + it "considers the current file to be at the target version if it exists and matches the checksum of the downloaded source file" do + @provider.load_current_resource + File.open(CHEF_SPEC_DATA + '/templates/seattle.txt') do |f| + @provider.matches_current_checksum?(f).should be_true + end + end + end end describe Chef::Provider::RemoteFile, "do_remote_file" do before(:each) do @rest = mock(Chef::REST, { }) - @tempfile = mock(Tempfile, { :path => "/tmp/foo", }) - @tempfile.stub!(:open).and_return(@tempfile) - @tempfile.stub!(:closed?).and_return(false) - @tempfile.stub!(:close) - @rest.stub!(:get_rest).and_return(@tempfile) + @tempfile = Tempfile.new("chef-rspec-remote_file_spec-line#{__LINE__}") + @rest.stub!(:fetch).and_yield(@tempfile) @resource = Chef::Resource::RemoteFile.new("seattle") - @resource.path(File.join(File.dirname(__FILE__), "..", "..", "data", "seattle.txt")) + @resource.path(File.expand_path(File.join(CHEF_SPEC_DATA, "seattle.txt"))) @resource.source("foo") @resource.cookbook_name = "monkey" @node = Chef::Node.new @@ -59,57 +70,60 @@ describe Chef::Provider::RemoteFile, "do_remote_file" do FileUtils.stub!(:cp).and_return(true) Chef::Platform.stub!(:find_platform_and_version).and_return([ :mac_os_x, "10.5.1" ]) end - + + after do + @tempfile.close! + end + def do_remote_file - Chef::REST.stub!(:new).and_return(@rest) + Chef::REST.stub!(:new).and_return(@rest) @provider.do_remote_file(@resource.source, @resource.path) end + + context "when the source is an absolute URI" do + before do + @resource.source("http://opscode.com/seattle.txt") + end + + describe "and the resource specifies a checksum" do - describe "when given a URI source" do - describe "and given a checksum" do it "should not download the file if the checksum matches" do @resource.checksum("0fd012fdc96e96f8f7cf2046522a54aed0ce470224513e45da6bc1a17a4924aa") - @resource.source("http://opscode.com/seattle.txt") - @rest.should_not_receive(:get_rest).with("http://opscode.com/seattle.txt", true).and_return(@tempfile) + @rest.should_not_receive(:fetch).with("http://opscode.com/seattle.txt").and_return(@tempfile) do_remote_file end it "should not download the file if the checksum is a partial match from the beginning" do @resource.checksum("0fd012fd") - @resource.source("http://opscode.com/seattle.txt") - @rest.should_not_receive(:get_rest).with("http://opscode.com/seattle.txt", true).and_return(@tempfile) + @rest.should_not_receive(:fetch).with("http://opscode.com/seattle.txt").and_return(@tempfile) do_remote_file end it "should download the file if the checksum does not match" do @resource.checksum("this hash doesn't match") - @resource.source("http://opscode.com/seattle.txt") - @rest.should_receive(:get_rest).with("http://opscode.com/seattle.txt", true).and_return(@tempfile) + @rest.should_receive(:fetch).with("http://opscode.com/seattle.txt").and_return(@tempfile) do_remote_file end it "should download the file if the checksum matches, but not from the beginning" do @resource.checksum("fd012fd") - @resource.source("http://opscode.com/seattle.txt") - @rest.should_receive(:get_rest).with("http://opscode.com/seattle.txt", true).and_return(@tempfile) + @rest.should_receive(:fetch).with("http://opscode.com/seattle.txt").and_return(@tempfile) do_remote_file end - end - describe "and not given a checksum" do + describe "and the resource doesn't specify a checksum" do it "should download the file from the remote URL" do @resource.checksum(nil) - @resource.source("http://opscode.com/seattle.txt") - @rest.should_receive(:get_rest).with("http://opscode.com/seattle.txt", true).and_return(@tempfile) + @rest.should_receive(:fetch).with("http://opscode.com/seattle.txt").and_return(@tempfile) do_remote_file end end end - - describe "when given a non-URI source" do + + describe "when the source is not an absolute URI" do describe "and using chef-solo" do it "should load the file from the local cookbook" do Chef::Config[:solo] = true @@ -117,12 +131,12 @@ describe Chef::Provider::RemoteFile, "do_remote_file" do @provider.should_receive(:find_preferred_file).with("monkey", :remote_file, @resource.source, "latte.local", nil, nil).and_return(@tempfile.path) do_remote_file end - + after(:each) do Chef::Config[:solo] = false end end - + it "should call generate_url with the current checksum as an extra attribute" do @provider.should_receive(:generate_url).with(@resource.source, "files", { :checksum => "0fd012fdc96e96f8f7cf2046522a54aed0ce470224513e45da6bc1a17a4924aa"}) do_remote_file @@ -135,7 +149,7 @@ describe Chef::Provider::RemoteFile, "do_remote_file" do url += "&fqdn=latte.local" url += "&node_name=latte" url += "&checksum=0fd012fdc96e96f8f7cf2046522a54aed0ce470224513e45da6bc1a17a4924aa" - @rest.should_receive(:get_rest).with(url, true).and_return(@tempfile) + @rest.should_receive(:fetch).with(url).and_yield(@tempfile) do_remote_file end end @@ -143,24 +157,24 @@ describe Chef::Provider::RemoteFile, "do_remote_file" do it "should not transfer the file if it has not been changed" do r = Net::HTTPNotModified.new("one", "two", "three") e = Net::HTTPRetriableError.new("304", r) - @rest.stub!(:get_rest).and_raise(e) + @rest.stub!(:fetch).and_raise(e) do_remote_file.should eql(false) end - + it "should raise an exception if it's any other kind of retriable response than 304" do r = Net::HTTPMovedPermanently.new("one", "two", "three") e = Net::HTTPRetriableError.new("301", r) - @rest.stub!(:get_rest).and_raise(e) + @rest.stub!(:fetch).and_raise(e) lambda { do_remote_file }.should raise_error(Net::HTTPRetriableError) end - + it "should raise an exception if anything else happens" do r = Net::HTTPBadRequest.new("one", "two", "three") e = Net::HTTPServerException.new("fake exception", r) - @rest.stub!(:get_rest).and_raise(e) - lambda { do_remote_file }.should raise_error(Net::HTTPServerException) + @rest.stub!(:fetch).and_raise(e) + lambda { do_remote_file }.should raise_error(Net::HTTPServerException) end - + it "should checksum the raw file" do @provider.should_receive(:checksum).with(@tempfile.path).and_return("0fd012fdc96e96f8f7cf2046522a54aed0ce470224513e45da6bc1a17a4924aa") do_remote_file @@ -173,16 +187,16 @@ describe Chef::Provider::RemoteFile, "do_remote_file" do end it "should copy the raw file to the new resource" do - FileUtils.should_receive(:cp).with(@tempfile.path, @resource.path).and_return(true) + FileUtils.should_receive(:cp).with(@tempfile.path, @resource.path).and_return(true) do_remote_file end it "should set the new resource to updated" do - @resource.should_receive(:updated=).with(true) + @resource.should_receive(:updated=).with(true) do_remote_file end end - + describe "when the target file already exists" do before do ::File.stub!(:exists?).with(@resource.path).and_return(true) @@ -202,12 +216,12 @@ describe Chef::Provider::RemoteFile, "do_remote_file" do end it "should copy the raw file to the new resource" do - FileUtils.should_receive(:cp).with(@tempfile.path, @resource.path).and_return(true) + FileUtils.should_receive(:cp).with(@tempfile.path, @resource.path).and_return(true) do_remote_file end it "should set the new resource to updated" do - @resource.should_receive(:updated=).with(true) + @resource.should_receive(:updated=).with(true) do_remote_file end end @@ -217,29 +231,23 @@ describe Chef::Provider::RemoteFile, "do_remote_file" do do_remote_file end end - + it "should set the owner if provided" do @resource.owner("adam") @provider.should_receive(:set_owner).and_return(true) do_remote_file end - + it "should set the group if provided" do @resource.group("adam") @provider.should_receive(:set_group).and_return(true) do_remote_file end - + it "should set the mode if provided" do @resource.mode(0676) @provider.should_receive(:set_mode).and_return(true) do_remote_file end - - it "should close the file when done" do - @tempfile.should_receive(:close) - do_remote_file - end -# TODO: Finish these tests end diff --git a/chef/spec/unit/provider/service/windows_spec.rb b/chef/spec/unit/provider/service/windows_spec.rb new file mode 100644 index 0000000000..12f8e83b6a --- /dev/null +++ b/chef/spec/unit/provider/service/windows_spec.rb @@ -0,0 +1,141 @@ +# +# 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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "spec_helper")) + +describe Chef::Provider::Service::Windows, "load_current_resource" do + before(:each) do + @init_command = "sc" + @node = mock("Chef::Node", :null_object => true) + @new_resource = Chef::Resource::Service.new("chef") + @new_resource.stub!(:pattern).and_return("chef") + @new_resource.stub!(:status_command).and_return(false) + + @current_resource = Chef::Resource::Service.new("chef") + @provider = Chef::Provider::Service::Windows.new(@node, @new_resource) + Chef::Resource::Service.stub!(:new).and_return(@current_resource) + IO.stub!(:popen).with("#{@init_command} query #{@new_resource.service_name}").and_return(['','','','4']) + IO.stub!(:popen).with("#{@init_command} qc #{@new_resource.service_name}").and_return(['','','','','2']) + end + + it "should create a current resource with the name of the new resource" do + Chef::Resource::Service.should_receive(:new).and_return(@current_resource) + @provider.load_current_resource + end + + it "should set the current resources service name to the new resources service name" do + @current_resource.should_receive(:service_name).with(@new_resource.service_name) + @provider.load_current_resource + end + + it "should return the current resource" do + @provider.load_current_resource.should eql(@current_resource) + end + +end + +describe Chef::Provider::Service::Windows, "start_service" do + before(:each) do + @new_resource = Chef::Resource::Service.new("chef") + @new_resource.start_command "sc start chef" + + @provider = Chef::Provider::Service::Windows.new(@node, @new_resource) + Chef::Resource::Service.stub!(:new).and_return(@current_resource) + end + + it "should call the start command if one is specified" do + @new_resource.stub!(:start_command).and_return("#{@new_resource.start_command}") + IO.should_receive(:popen).with("#{@new_resource.start_command}").and_return(StringIO.new("foo\nbar\nbaz\n2 START_PENDING\n")) + @provider.start_service() + end +end + +describe Chef::Provider::Service::Windows, "stop_service" do + before(:each) do + @init_command = "sc" + @new_resource = Chef::Resource::Service.new("chef") + @new_resource.stop_command "sc stop chef" + + @provider = Chef::Provider::Service::Windows.new(@node, @new_resource) + Chef::Resource::Service.stub!(:new).and_return(@current_resource) + end + + it "should call the stop command if one is specified" do + @new_resource.stub!(:stop_command).and_return("#{@new_resource.stop_command}") + IO.should_receive(:popen).with("#{@new_resource.stop_command}").and_return(StringIO.new("foo\nbar\nbaz\n1 STOPPED\n")) + @provider.stop_service().should be_true + end + + it "should use the built-in command if no stop command is specified" do + @new_resource.stub!(:stop_command).and_return(nil) + IO.stub!(:popen).with("#{@init_command} stop #{@new_resource.service_name}").and_return(IO.new(2,'w')) + IO.popen("#{@init_command} stop #{@new_resource.service_name}").stub!(:readlines).and_return(["foo\n","bar\n","baz\n","1 STOPPED\n"]) + @provider.stop_service().should be_true + end +end + +describe Chef::Provider::Service::Windows, "restart_service" do + before(:each) do + @init_command = "sc" + @new_resource = Chef::Resource::Service.new("chef") + + @provider = Chef::Provider::Service::Windows.new(@node, @new_resource) + Chef::Resource::Service.stub!(:new).and_return(@current_resource) + end + + it "should just call stop, then start when the resource doesn't support restart and no restart_command is specified" do + IO.should_receive(:popen).with("#{@init_command} stop #{@new_resource.service_name}").and_return(StringIO.new("foo\nbar\nbaz\n1 STOPPED\n")) + IO.should_receive(:popen).with("#{@init_command} start #{@new_resource.service_name}").and_return(StringIO.new("foo\nbar\nbaz\n2 START_PENDING\n")) + @provider.restart_service() + end +end + +describe Chef::Provider::Service::Windows, "enable_service" do + before(:each) do + @init_command = "sc" + @node = Chef::Node.new + @new_resource = Chef::Resource::Service.new("chef") + @new_resource.running false + @new_resource.enabled false + + @provider = Chef::Provider::Service::Windows.new(@node, @new_resource) + Chef::Resource::Service.stub!(:new).and_return(@current_resource) + end + + it "should enable service and set the startup type" do + @new_resource.startup_type :automatic + IO.should_receive(:popen).with("sc config chef start= auto").and_return(StringIO.new("SUCCESS")) + @provider.enable_service().should be_true + end +end + +describe Chef::Provider::Service::Windows, "disable_service" do + before(:each) do + @node = Chef::Node.new + @new_resource = Chef::Resource::Service.new("chef") + + @provider = Chef::Provider::Service::Windows.new(@node, @new_resource) + Chef::Resource::Service.stub!(:new).and_return(@current_resource) + end + + it "should disable service" do + IO.should_receive(:popen).with("sc config chef start= disabled").and_return(StringIO.new("SUCCESS")) + @provider.disable_service().should be_true + end +end + diff --git a/chef/spec/unit/provider/subversion_spec.rb b/chef/spec/unit/provider/subversion_spec.rb index 6a909d1c12..0672e5a629 100644 --- a/chef/spec/unit/provider/subversion_spec.rb +++ b/chef/spec/unit/provider/subversion_spec.rb @@ -99,6 +99,7 @@ describe Chef::Provider::Subversion do before do @stdout = mock("stdout") @stderr = mock("stderr") + @resource.svn_info_args "--no-auth-cache" end it "returns the revision number as is if it's already an integer" do @@ -120,7 +121,9 @@ describe Chef::Provider::Subversion do @resource.revision "HEAD" @stdout.stub!(:string).and_return(example_svn_info) @stderr.stub!(:string).and_return("") - @provider.should_receive(:popen4).and_yield("no-pid","no-stdin",@stdout,@stderr). + expected_command = ["svn info http://svn.example.org/trunk/ --no-auth-cache -rHEAD", {:cwd=>Dir.tmpdir}] + @provider.should_receive(:popen4).with(*expected_command). + and_yield("no-pid","no-stdin",@stdout,@stderr). and_return(exitstatus) @provider.revision_int.should eql("11410") end diff --git a/chef/spec/unit/provider/template_spec.rb b/chef/spec/unit/provider/template_spec.rb index 1826415eb8..a39d884e0f 100644 --- a/chef/spec/unit/provider/template_spec.rb +++ b/chef/spec/unit/provider/template_spec.rb @@ -15,19 +15,20 @@ # See the License for the specific language governing permissions and # limitations under the License. # - +require 'stringio' require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper")) describe Chef::Provider::Template do before(:each) do @rest = mock(Chef::REST, { :get_rest => "/tmp/foobar" }) - @tempfile = mock(Tempfile, { :path => "/tmp/foo", :print => true, :close => true }) - Tempfile.stub!(:new).and_return(@tempfile) + @tempfile = StringIO.new + @tempfile.stub!(:path).and_return("/tmp/foo") + Tempfile.stub!(:open).and_yield(@tempfile) File.stub!(:read).and_return("monkeypoop") @rest.stub!(:get_rest).and_return(@tempfile) @resource = Chef::Resource::Template.new("seattle") @resource.cookbook_name = "foo" - @resource.path(File.join(File.dirname(__FILE__), "..", "..", "data", "templates", "seattle.txt")) + @resource.path(CHEF_SPEC_DATA + '/templates/seattle.txt') @resource.source("http://foo") @node = Chef::Node.new @node.name "latte" @@ -35,7 +36,7 @@ describe Chef::Provider::Template do @provider.stub!(:checksum).and_return("0fd012fdc96e96f8f7cf2046522a54aed0ce470224513e45da6bc1a17a4924aa") @provider.current_resource = @resource.clone @provider.current_resource.checksum("0fd012fdc96e96f8f7cf2046522a54aed0ce470224513e45da6bc1a17a4924aa") - FileUtils.stub!(:cp).and_return(true) + FileUtils.stub!(:mv).and_return(true) Chef::FileCache.stub!(:has_key).and_return(false) Chef::FileCache.stub!(:move_to).and_return(true) Chef::FileCache.stub!(:load).and_return("monkeypoop") @@ -77,9 +78,10 @@ describe Chef::Provider::Template do end it "should set the checksum of the new resource to the value of the returned template" do - @resource.should_receive(:checksum).with("0fd012fdc96e96f8f7cf2046522a54aed0ce470224513e45da6bc1a17a4924aa").once - @resource.should_receive(:checksum).twice + #@resource.should_receive(:checksum).with("0fd012fdc96e96f8f7cf2046522a54aed0ce470224513e45da6bc1a17a4924aa").once + #@resource.should_receive(:checksum).twice do_action_create + @resource.checksum.should == "0fd012fdc96e96f8f7cf2046522a54aed0ce470224513e45da6bc1a17a4924aa" end it "should not copy the tempfile to the real file if the checksums match" do @@ -89,7 +91,7 @@ describe Chef::Provider::Template do it "should copy the tempfile to the real file if the checksums do not match" do @provider.stub!(:checksum).and_return("0fd012fdc96e96f8f7cf2046522a54aed0ce470224513e45da6bc1a17a4924ab") - FileUtils.should_receive(:cp).once + FileUtils.should_receive(:mv).with("/tmp/foo", CHEF_SPEC_DATA + '/templates/seattle.txt').once @provider.stub!(:backup).and_return(true) do_action_create end @@ -140,6 +142,9 @@ describe Chef::Provider::Template do it "should not update the FileCache for the template on the second pass" do do_action_create Chef::FileCache.should_not_receive(:move_to) + @tempfile = StringIO.new + @tempfile.stub!(:path).and_return("/tmp/foo") + Tempfile.stub!(:open).and_yield(@tempfile) do_action_create end end diff --git a/chef/spec/unit/provider_spec.rb b/chef/spec/unit/provider_spec.rb index 19c444c1ab..d4f2c7b3e5 100644 --- a/chef/spec/unit/provider_spec.rb +++ b/chef/spec/unit/provider_spec.rb @@ -54,7 +54,7 @@ describe Chef::Provider do it "evals embedded recipes with a pristine resource collection" do @provider.instance_variable_set(:@collection, "bouncyCastle") temporary_collection = nil - snitch = lambda {temporary_collection = @collection} + snitch = Proc.new {temporary_collection = @collection} @provider.send(:recipe_eval, &snitch) temporary_collection.should be_an_instance_of(Chef::ResourceCollection) @provider.instance_variable_get(:@collection).should == "bouncyCastle" diff --git a/chef/spec/unit/recipe_spec.rb b/chef/spec/unit/recipe_spec.rb index 8805939b90..7f9e52ca66 100644 --- a/chef/spec/unit/recipe_spec.rb +++ b/chef/spec/unit/recipe_spec.rb @@ -146,7 +146,7 @@ describe Chef::Recipe do describe "from_file" do it "should load a resource from a ruby file" do - @recipe.from_file(File.join(File.dirname(__FILE__), "..", "data", "recipes", "test.rb")) + @recipe.from_file(File.join(CHEF_SPEC_DATA, "recipes", "test.rb")) res = @recipe.resources(:file => "/etc/nsswitch.conf") res.name.should eql("/etc/nsswitch.conf") res.action.should eql([:create]) @@ -162,7 +162,7 @@ describe Chef::Recipe do describe "include_recipe" do it "should evaluate another recipe with include_recipe" do - Chef::Config.cookbook_path File.join(File.dirname(__FILE__), "..", "data", "cookbooks") + Chef::Config.cookbook_path File.join(CHEF_SPEC_DATA, "cookbooks") @recipe.cookbook_loader.load_cookbooks @recipe.include_recipe "openldap::gigantor" res = @recipe.resources(:cat => "blanket") @@ -171,7 +171,7 @@ describe Chef::Recipe do end it "should load the default recipe for a cookbook if include_recipe is called without a ::" do - Chef::Config.cookbook_path File.join(File.dirname(__FILE__), "..", "data", "cookbooks") + Chef::Config.cookbook_path File.join(CHEF_SPEC_DATA, "cookbooks") @recipe.cookbook_loader.load_cookbooks @recipe.include_recipe "openldap" res = @recipe.resources(:cat => "blanket") @@ -180,14 +180,14 @@ describe Chef::Recipe do end it "should store that it has seen a recipe in node.run_state[:seen_recipes]" do - Chef::Config.cookbook_path File.join(File.dirname(__FILE__), "..", "data", "cookbooks") + Chef::Config.cookbook_path File.join(CHEF_SPEC_DATA, "cookbooks") @recipe.cookbook_loader.load_cookbooks @recipe.include_recipe "openldap" @node.run_state[:seen_recipes].should have_key("openldap") end it "should not include the same recipe twice" do - Chef::Config.cookbook_path File.join(File.dirname(__FILE__), "..", "data", "cookbooks") + Chef::Config.cookbook_path File.join(CHEF_SPEC_DATA, "cookbooks") @recipe.cookbook_loader.load_cookbooks @recipe.include_recipe "openldap" Chef::Log.should_receive(:debug).with("I am not loading openldap, because I have already seen it.") @@ -243,4 +243,4 @@ describe Chef::Recipe do @recipe.node[:tags].should eql([]) end end -end
\ No newline at end of file +end diff --git a/chef/spec/unit/resource/deploy_spec.rb b/chef/spec/unit/resource/deploy_spec.rb index 40d6507bf5..7c4d945431 100644 --- a/chef/spec/unit/resource/deploy_spec.rb +++ b/chef/spec/unit/resource/deploy_spec.rb @@ -69,6 +69,7 @@ describe Chef::Resource::Deploy do resource_has_a_string_attribute(:svn_username) resource_has_a_string_attribute(:svn_password) resource_has_a_string_attribute(:svn_arguments) + resource_has_a_string_attribute(:svn_info_args) resource_has_a_boolean_attribute(:migrate, :defaults_to=>false) resource_has_a_boolean_attribute(:enable_submodules, :defaults_to=>false) diff --git a/chef/spec/unit/resource/scm_spec.rb b/chef/spec/unit/resource/scm_spec.rb index 11b10905ec..a84543eec4 100644 --- a/chef/spec/unit/resource/scm_spec.rb +++ b/chef/spec/unit/resource/scm_spec.rb @@ -6,9 +6,9 @@ # 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. @@ -19,15 +19,15 @@ require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper")) describe Chef::Resource::Scm do - + before(:each) do @resource = Chef::Resource::Scm.new("my awesome app") end - + it "should be a SCM resource" do @resource.should be_a_kind_of(Chef::Resource::Scm) end - + it "supports :checkout, :export, :sync, :diff, and :log actions" do @resource.allowed_actions.should include(:checkout) @resource.allowed_actions.should include(:export) @@ -35,99 +35,105 @@ describe Chef::Resource::Scm do @resource.allowed_actions.should include(:diff) @resource.allowed_actions.should include(:log) end - + it "takes the destination path as a string" do @resource.destination "/path/to/deploy/dir" @resource.destination.should eql("/path/to/deploy/dir") end - + it "takes a string for the repository URL" do @resource.repository "git://github.com/opscode/chef.git" @resource.repository.should eql("git://github.com/opscode/chef.git") end - + it "takes a string for the revision" do @resource.revision "abcdef" @resource.revision.should eql("abcdef") end - + it "defaults to the ``HEAD'' revision" do @resource.revision.should eql("HEAD") end - + it "takes a string for the user to run as" do @resource.user "dr_deploy" @resource.user.should eql("dr_deploy") end - + it "also takes an integer for the user to run as" do @resource.user 0 @resource.user.should eql(0) end - + it "takes a string for the group to run as, defaulting to nil" do @resource.group.should be_nil @resource.group "opsdevs" @resource.group.should == "opsdevs" end - + it "also takes an integer for the group to run as" do @resource.group 23 @resource.group.should == 23 end - + it "has a svn_username String attribute" do @resource.svn_username "moartestsplz" @resource.svn_username.should eql("moartestsplz") end - + it "has a svn_password String attribute" do @resource.svn_password "taftplz" @resource.svn_password.should eql("taftplz") end - + it "has a svn_arguments String attribute" do @resource.svn_arguments "--more-taft plz" @resource.svn_arguments.should eql("--more-taft plz") end - + + it "has a svn_info_args String attribute" do + @resource.svn_info_args.should be_nil + @resource.svn_info_args("--no-moar-plaintext-creds yep") + @resource.svn_info_args.should == "--no-moar-plaintext-creds yep" + end + it "takes the depth as an integer for shallow clones" do @resource.depth 5 @resource.depth.should == 5 lambda {@resource.depth "five"}.should raise_error(ArgumentError) end - + it "defaults to nil depth for a full clone" do @resource.depth.should be_nil end - + it "takes a boolean for #enable_submodules" do @resource.enable_submodules true @resource.enable_submodules.should be_true lambda {@resource.enable_submodules "lolz"}.should raise_error(ArgumentError) end - + it "defaults to not enabling submodules" do @resource.enable_submodules.should be_false end - + it "takes a string for the remote" do @resource.remote "opscode" @resource.remote.should eql("opscode") lambda {@resource.remote 1337}.should raise_error(ArgumentError) end - + it "defaults to ``origin'' for the remote" do @resource.remote.should == "origin" end - + it "takes a string for the ssh wrapper" do @resource.ssh_wrapper "with_ssh_fu" @resource.ssh_wrapper.should eql("with_ssh_fu") end - + it "defaults to nil for the ssh wrapper" do @resource.ssh_wrapper.should be_nil end - -end
\ No newline at end of file + +end diff --git a/chef/spec/unit/resource_definition_spec.rb b/chef/spec/unit/resource_definition_spec.rb index ca51f113d9..fc42210a8c 100644 --- a/chef/spec/unit/resource_definition_spec.rb +++ b/chef/spec/unit/resource_definition_spec.rb @@ -106,7 +106,7 @@ describe Chef::ResourceDefinition do end it "should load a description from a file" do - @def.from_file(File.join(File.dirname(__FILE__), "..", "data", "definitions", "test.rb")) + @def.from_file(File.join(CHEF_SPEC_DATA, "definitions", "test.rb")) @def.name.should eql(:rico_suave) @def.params[:rich].should eql("smooth") end @@ -116,4 +116,4 @@ describe Chef::ResourceDefinition do @def.to_s.should eql("woot") end -end
\ No newline at end of file +end diff --git a/chef/spec/unit/rest/auth_credentials_spec.rb b/chef/spec/unit/rest/auth_credentials_spec.rb new file mode 100644 index 0000000000..7b14f43b92 --- /dev/null +++ b/chef/spec/unit/rest/auth_credentials_spec.rb @@ -0,0 +1,282 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Author:: Christopher Brown (<cb@opscode.com>) +# Author:: Daniel DeLeo (<dan@opscode.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 File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper")) +require 'uri' +require 'net/https' + +KEY_DOT_PEM=<<-END_RSA_KEY +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA49TA0y81ps0zxkOpmf5V4/c4IeR5yVyQFpX3JpxO4TquwnRh +8VSUhrw8kkTLmB3cS39Db+3HadvhoqCEbqPE6915kXSuk/cWIcNozujLK7tkuPEy +YVsyTioQAddSdfe+8EhQVf3oHxaKmUd6waXrWqYCnhxgOjxocenREYNhZ/OETIei +PbOku47vB4nJK/0GhKBytL2XnsRgfKgDxf42BqAi1jglIdeq8lAWZNF9TbNBU21A +O1iuT7Pm6LyQujhggPznR5FJhXKRUARXBJZawxpGV4dGtdcahwXNE4601aXPra+x +PcRd2puCNoEDBzgVuTSsLYeKBDMSfs173W1QYwIDAQABAoIBAGF05q7vqOGbMaSD +2Q7YbuE/JTHKTBZIlBI1QC2x+0P5GDxyEFttNMOVzcs7xmNhkpRw8eX1LrInrpMk +WsIBKAFFEfWYlf0RWtRChJjNl+szE9jQxB5FJnWtJH/FHa78tR6PsF24aQyzVcJP +g0FGujBihwgfV0JSCNOBkz8MliQihjQA2i8PGGmo4R4RVzGfxYKTIq9vvRq/+QEa +Q4lpVLoBqnENpnY/9PTl6JMMjW2b0spbLjOPVwDaIzXJ0dChjNXo15K5SHI5mALJ +I5gN7ODGb8PKUf4619ez194FXq+eob5YJdilTFKensIUvt3YhP1ilGMM+Chi5Vi/ +/RCTw3ECgYEA9jTw4wv9pCswZ9wbzTaBj9yZS3YXspGg26y6Ohq3ZmvHz4jlT6uR +xK+DDcUiK4072gci8S4Np0fIVS7q6ivqcOdzXPrTF5/j+MufS32UrBbUTPiM1yoO +ECcy+1szl/KoLEV09bghPbvC58PFSXV71evkaTETYnA/F6RK12lEepcCgYEA7OSy +bsMrGDVU/MKJtwqyGP9ubA53BorM4Pp9VVVSCrGGVhb9G/XNsjO5wJC8J30QAo4A +s59ZzCpyNRy046AB8jwRQuSwEQbejSdeNgQGXhZ7aIVUtuDeFFdaIz/zjVgxsfj4 +DPOuzieMmJ2MLR4F71ocboxNoDI7xruPSE8dDhUCgYA3vx732cQxgtHwAkeNPJUz +dLiE/JU7CnxIoSB9fYUfPLI+THnXgzp7NV5QJN2qzMzLfigsQcg3oyo6F2h7Yzwv +GkjlualIRRzCPaCw4Btkp7qkPvbs1QngIHALt8fD1N69P3DPHkTwjG4COjKWgnJq +qoHKS6Fe/ZlbigikI6KsuwKBgQCTlSLoyGRHr6oj0hqz01EDK9ciMJzMkZp0Kvn8 +OKxlBxYW+jlzut4MQBdgNYtS2qInxUoAnaz2+hauqhSzntK3k955GznpUatCqx0R +b857vWviwPX2/P6+E3GPdl8IVsKXCvGWOBZWTuNTjQtwbDzsUepWoMgXnlQJSn5I +YSlLxQKBgQD16Gw9kajpKlzsPa6XoQeGmZALT6aKWJQlrKtUQIrsIWM0Z6eFtX12 +2jjHZ0awuCQ4ldqwl8IfRogWMBkHOXjTPVK0YKWWlxMpD/5+bGPARa5fir8O1Zpo +Y6S6MeZ69Rp89ma4ttMZ+kwi1+XyHqC/dlcVRW42Zl5Dc7BALRlJjQ== +-----END RSA PRIVATE KEY----- + END_RSA_KEY + + +describe Chef::REST::AuthCredentials do + before do + @key_file_fixture = CHEF_SPEC_DATA + '/ssl/private_key.pem' + @auth_credentials = Chef::REST::AuthCredentials.new("client-name", @key_file_fixture) + end + + it "has a key file value" do + @auth_credentials.key_file.should == @key_file_fixture + end + + it "has a client name" do + @auth_credentials.client_name.should == "client-name" + end + + it "loads the private key when initialized with the path to the key" do + @auth_credentials.key.should respond_to :private_encrypt + @auth_credentials.key.to_s.should == KEY_DOT_PEM + end + + describe "when loading the private key" do + it "raises PrivateKeyMissing when the key file doesn't exist" do + lambda {Chef::REST::AuthCredentials.new("client-name", "/dev/null/nothing_here")}.should raise_error(Chef::Exceptions::PrivateKeyMissing) + end + + it "raises InvalidPrivateKey when the key file doesnt' look like a key" do + invalid_key_file = CHEF_SPEC_DATA + "/bad-config.rb" + lambda {Chef::REST::AuthCredentials.new("client-name", invalid_key_file)}.should raise_error(Chef::Exceptions::InvalidPrivateKey) + end + end + + describe "generating signature headers for a request" do + before do + @request_time = Time.at(1270920860) + @request_params = {:http_method => :POST, :path => "/clients", :body => '{"some":"json"}', :host => "localhost"} + end + + it "generates signature headers for the request" do + Time.stub!(:now).and_return(@request_time) + expected = {} + expected["HOST"] = "localhost" + expected["X-OPS-AUTHORIZATION-1"] = "kBssX1ENEwKtNYFrHElN9vYGWS7OeowepN9EsYc9csWfh8oUovryPKDxytQ/" + expected["X-OPS-AUTHORIZATION-2"] = "Wc2/nSSyxdWJjjfHzrE+YrqNQTaArOA7JkAf5p75eTUonCWcvNPjFrZVgKGS" + expected["X-OPS-AUTHORIZATION-3"] = "yhzHJQh+lcVA9wwARg5Hu9q+ddS8xBOdm3Vp5atl5NGHiP0loiigMYvAvzPO" + expected["X-OPS-AUTHORIZATION-4"] = "r9853eIxwYMhn5hLGhAGFQznJbE8+7F/lLU5Zmk2t2MlPY8q3o1Q61YD8QiJ" + expected["X-OPS-AUTHORIZATION-5"] = "M8lIt53ckMyUmSU0DDURoiXLVkE9mag/6Yq2tPNzWq2AdFvBqku9h2w+DY5k" + expected["X-OPS-AUTHORIZATION-6"] = "qA5Rnzw5rPpp3nrWA9jKkPw4Wq3+4ufO2Xs6w7GCjA==" + expected["X-OPS-CONTENT-HASH"] = "1tuzs5XKztM1ANrkGNPah6rW9GY=" + expected["X-OPS-SIGN"] = "version=1.0" + expected["X-OPS-TIMESTAMP"] = "2010-04-10T17:34:20Z" + expected["X-OPS-USERID"] = "client-name" + + @auth_credentials.signature_headers(@request_params).should == expected + end + end +end + +describe Chef::REST::RESTRequest do + def new_request(method=nil) + method ||= :POST + Chef::REST::RESTRequest.new(method, @url, @req_body, @headers) + end + + before do + @auth_credentials = Chef::REST::AuthCredentials.new("client-name", CHEF_SPEC_DATA + '/ssl/private_key.pem') + @url = URI.parse("http://chef.example.com:4000/?q=chef_is_awesome") + @req_body = '{"json_data":"as_a_string"}' + @headers = {"Content-type" =>"application/json", "Accept"=>"application/json"} + @request = Chef::REST::RESTRequest.new(:POST, @url, @req_body, @headers) + end + + it "stores the url it was created with" do + @request.url.should == @url + end + + it "stores the HTTP method" do + @request.method.should == :POST + end + + it "adds the chef version header" do + @request.headers.should == @headers.merge("X-Chef-Version" => ::Chef::VERSION) + end + + describe "configuring the HTTP request" do + it "configures GET requests" do + @req_body = nil + rest_req = new_request(:GET) + rest_req.http_request.should be_a_kind_of(Net::HTTP::Get) + rest_req.http_request.path.should == "/?q=chef_is_awesome" + rest_req.http_request.body.should be_nil + end + + it "configures POST requests, including the body" do + @request.http_request.should be_a_kind_of(Net::HTTP::Post) + @request.http_request.path.should == "/?q=chef_is_awesome" + @request.http_request.body.should == @req_body + end + + it "configures PUT requests, including the body" do + rest_req = new_request(:PUT) + rest_req.http_request.should be_a_kind_of(Net::HTTP::Put) + rest_req.http_request.path.should == "/?q=chef_is_awesome" + rest_req.http_request.body.should == @req_body + end + + it "configures DELETE requests" do + rest_req = new_request(:DELETE) + rest_req.http_request.should be_a_kind_of(Net::HTTP::Delete) + rest_req.http_request.path.should == "/?q=chef_is_awesome" + rest_req.http_request.body.should be_nil + end + + it "configures HTTP basic auth" do + @url = URI.parse("http://homie:theclown@chef.example.com:4000/?q=chef_is_awesome") + rest_req = new_request(:GET) + rest_req.http_request.to_hash["authorization"].should == ["Basic aG9taWU6dGhlY2xvd24="] + end + end + + describe "configuring the HTTP client" do + it "configures the HTTP client for the host and port" do + http_client = new_request.http_client + http_client.address.should == "chef.example.com" + http_client.port.should == 4000 + end + + it "configures the HTTP client with the read timeout set in the config file" do + Chef::Config[:rest_timeout] = 9001 + new_request.http_client.read_timeout.should == 9001 + end + + describe "for SSL" do + before do + Chef::Config[:ssl_client_cert] = nil + Chef::Config[:ssl_client_key] = nil + Chef::Config[:ssl_ca_path] = nil + Chef::Config[:ssl_ca_file] = nil + end + + after do + Chef::Config[:ssl_client_cert] = nil + Chef::Config[:ssl_client_key] = nil + Chef::Config[:ssl_ca_path] = nil + Chef::Config[:ssl_verify_mode] = :verify_none + Chef::Config[:ssl_ca_file] = nil + end + + describe "when configured with :ssl_verify_mode set to :verify peer" do + before do + @url = URI.parse("https://chef.example.com:4443/") + Chef::Config[:ssl_verify_mode] = :verify_peer + @request = new_request + end + + it "configures the HTTP client to use SSL when given a URL with the https protocol" do + @request.http_client.use_ssl?.should be_true + end + + it "sets the OpenSSL verify mode to verify_peer" do + @request.http_client.verify_mode.should == OpenSSL::SSL::VERIFY_PEER + end + + it "raises a ConfigurationError if :ssl_ca_path is set to a path that doesn't exist" do + Chef::Config[:ssl_ca_path] = "/dev/null/nothing_here" + lambda {new_request}.should raise_error(Chef::Exceptions::ConfigurationError) + end + + it "should set the CA path if that is set in the configuration" do + Chef::Config[:ssl_ca_path] = File.join(CHEF_SPEC_DATA, "ssl") + new_request.http_client.ca_path.should == File.join(CHEF_SPEC_DATA, "ssl") + end + + it "raises a ConfigurationError if :ssl_ca_file is set to a file that does not exist" do + Chef::Config[:ssl_ca_file] = "/dev/null/nothing_here" + lambda {new_request}.should raise_error(Chef::Exceptions::ConfigurationError) + end + + it "should set the CA file if that is set in the configuration" do + Chef::Config[:ssl_ca_file] = CHEF_SPEC_DATA + '/ssl/5e707473.0' + new_request.http_client.ca_file.should == CHEF_SPEC_DATA + '/ssl/5e707473.0' + end + end + + describe "when configured with :ssl_verify_mode set to :verify peer" do + before do + @url = URI.parse("https://chef.example.com:4443/") + Chef::Config[:ssl_verify_mode] = :verify_none + end + + it "sets the OpenSSL verify mode to :verify_none" do + new_request.http_client.verify_mode.should == OpenSSL::SSL::VERIFY_NONE + end + end + + describe "when configured with a client certificate" do + before {@url = URI.parse("https://chef.example.com:4443/")} + + it "raises ConfigurationError if the certificate file doesn't exist" do + Chef::Config[:ssl_client_cert] = "/dev/null/nothing_here" + Chef::Config[:ssl_client_key] = CHEF_SPEC_DATA + '/ssl/chef-rspec.key' + lambda {new_request}.should raise_error(Chef::Exceptions::ConfigurationError) + end + + it "raises ConfigurationError if the certificate file doesn't exist" do + Chef::Config[:ssl_client_cert] = CHEF_SPEC_DATA + '/ssl/chef-rspec.cert' + Chef::Config[:ssl_client_key] = "/dev/null/nothing_here" + lambda {new_request}.should raise_error(Chef::Exceptions::ConfigurationError) + end + + it "raises a ConfigurationError if one of :ssl_client_cert and :ssl_client_key is set but not both" do + Chef::Config[:ssl_client_cert] = "/dev/null/nothing_here" + Chef::Config[:ssl_client_key] = nil + lambda {new_request}.should raise_error(Chef::Exceptions::ConfigurationError) + end + + it "configures the HTTP client's cert and private key" do + Chef::Config[:ssl_client_cert] = CHEF_SPEC_DATA + '/ssl/chef-rspec.cert' + Chef::Config[:ssl_client_key] = CHEF_SPEC_DATA + '/ssl/chef-rspec.key' + http_client = new_request.http_client + http_client.cert.to_s.should == OpenSSL::X509::Certificate.new(IO.read(CHEF_SPEC_DATA + '/ssl/chef-rspec.cert')).to_s + http_client.key.to_s.should == IO.read(CHEF_SPEC_DATA + '/ssl/chef-rspec.key') + end + end + end + end + +end
\ No newline at end of file diff --git a/chef/spec/unit/rest_spec.rb b/chef/spec/unit/rest_spec.rb index 254ae832b7..f36a020fca 100644 --- a/chef/spec/unit/rest_spec.rb +++ b/chef/spec/unit/rest_spec.rb @@ -1,15 +1,17 @@ # # Author:: Adam Jacob (<adam@opscode.com>) # Author:: Christopher Brown (<cb@opscode.com>) +# Author:: Daniel DeLeo (<dan@opscode.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. @@ -20,403 +22,539 @@ require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper")) require 'uri' require 'net/https' +require 'stringio' + +SIGNING_KEY_DOT_PEM=<<-END_RSA_KEY +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEA49TA0y81ps0zxkOpmf5V4/c4IeR5yVyQFpX3JpxO4TquwnRh +8VSUhrw8kkTLmB3cS39Db+3HadvhoqCEbqPE6915kXSuk/cWIcNozujLK7tkuPEy +YVsyTioQAddSdfe+8EhQVf3oHxaKmUd6waXrWqYCnhxgOjxocenREYNhZ/OETIei +PbOku47vB4nJK/0GhKBytL2XnsRgfKgDxf42BqAi1jglIdeq8lAWZNF9TbNBU21A +O1iuT7Pm6LyQujhggPznR5FJhXKRUARXBJZawxpGV4dGtdcahwXNE4601aXPra+x +PcRd2puCNoEDBzgVuTSsLYeKBDMSfs173W1QYwIDAQABAoIBAGF05q7vqOGbMaSD +2Q7YbuE/JTHKTBZIlBI1QC2x+0P5GDxyEFttNMOVzcs7xmNhkpRw8eX1LrInrpMk +WsIBKAFFEfWYlf0RWtRChJjNl+szE9jQxB5FJnWtJH/FHa78tR6PsF24aQyzVcJP +g0FGujBihwgfV0JSCNOBkz8MliQihjQA2i8PGGmo4R4RVzGfxYKTIq9vvRq/+QEa +Q4lpVLoBqnENpnY/9PTl6JMMjW2b0spbLjOPVwDaIzXJ0dChjNXo15K5SHI5mALJ +I5gN7ODGb8PKUf4619ez194FXq+eob5YJdilTFKensIUvt3YhP1ilGMM+Chi5Vi/ +/RCTw3ECgYEA9jTw4wv9pCswZ9wbzTaBj9yZS3YXspGg26y6Ohq3ZmvHz4jlT6uR +xK+DDcUiK4072gci8S4Np0fIVS7q6ivqcOdzXPrTF5/j+MufS32UrBbUTPiM1yoO +ECcy+1szl/KoLEV09bghPbvC58PFSXV71evkaTETYnA/F6RK12lEepcCgYEA7OSy +bsMrGDVU/MKJtwqyGP9ubA53BorM4Pp9VVVSCrGGVhb9G/XNsjO5wJC8J30QAo4A +s59ZzCpyNRy046AB8jwRQuSwEQbejSdeNgQGXhZ7aIVUtuDeFFdaIz/zjVgxsfj4 +DPOuzieMmJ2MLR4F71ocboxNoDI7xruPSE8dDhUCgYA3vx732cQxgtHwAkeNPJUz +dLiE/JU7CnxIoSB9fYUfPLI+THnXgzp7NV5QJN2qzMzLfigsQcg3oyo6F2h7Yzwv +GkjlualIRRzCPaCw4Btkp7qkPvbs1QngIHALt8fD1N69P3DPHkTwjG4COjKWgnJq +qoHKS6Fe/ZlbigikI6KsuwKBgQCTlSLoyGRHr6oj0hqz01EDK9ciMJzMkZp0Kvn8 +OKxlBxYW+jlzut4MQBdgNYtS2qInxUoAnaz2+hauqhSzntK3k955GznpUatCqx0R +b857vWviwPX2/P6+E3GPdl8IVsKXCvGWOBZWTuNTjQtwbDzsUepWoMgXnlQJSn5I +YSlLxQKBgQD16Gw9kajpKlzsPa6XoQeGmZALT6aKWJQlrKtUQIrsIWM0Z6eFtX12 +2jjHZ0awuCQ4ldqwl8IfRogWMBkHOXjTPVK0YKWWlxMpD/5+bGPARa5fir8O1Zpo +Y6S6MeZ69Rp89ma4ttMZ+kwi1+XyHqC/dlcVRW42Zl5Dc7BALRlJjQ== +-----END RSA PRIVATE KEY----- + END_RSA_KEY + describe Chef::REST do before(:each) do + @log_stringio = StringIO.new + @logger = Logger.new(@log_stringio) + @original_chef_logger = Chef::Log.logger + Chef::Log.logger = @logger + Chef::REST::CookieJar.stub!(:instance).and_return({}) - @rest = Chef::REST.new("url", nil, nil) - end + @base_url = "http://chef.example.com:4000" + @monkey_uri = URI.parse("http://chef.example.com:4000/monkey") + @rest = Chef::REST.new(@base_url, nil, nil) - describe "load_signing_key" do - before(:each) do - @private_key = <<EOH ------BEGIN RSA PRIVATE KEY----- -MIIEowIBAAKCAQEAx8xAfO2BO8kUughpjWwHPN2rgDcES15PbMEGe6OdJgjFARkt -FMdEusbGxmXKpk51Ggxi2P6ZYEoZfniZWt4qSt4i1vanDayRlJ1qoRCOaYj5cQS7 -gpHspHWqkY3HfGvx4svdutQ06o/gypx2QYfi68YrIUQexPiTUhsnP9FlgNt40Rl1 -YgBiIlJUk7d3q+1b/+POTNKPeyjGK9hoTloplbSx+cYdZgc4/YpU0eLBoHPuPv5l -QD+Y8VNS39bvY2NWbCqhV508gExAK26FxXTDNpi2mTZmbRZ8U0PKrCgF6gBSeod5 -EdQnNgoZHmA2fzfPHWfJd2OEuMcNM7DWpPDizQIDAQABAoIBAAGVDYGvw9E8Y2yh -umxDSb9ipgQK637JTWm4EZwTDKCLezvp/iBm/5VXE6XoknVEs8q0BGhhg8dubstA -mz5L+hvDrJT1ORdzoWeC46BI6EfPrOIHPpDnJO+cevBSJh1HIZBBOw1KtuyQnSAd -oxYbxGFHnXnS90dqDIie7G2l897UWoiQWNMLY+A+l5H4GLC+4Phq02pLd4OQwXA3 -Nd+3Nq69aOeccyfSDeeG7u35TKrjQPIxU210aR18d/0trR20BKsKbT30GPE1tQQd -jm4uReSPttTQ+NjwBQKKYmO2F9b9MPzmQ7c+KycBRmf+IOgZeZ54JN0GzUXsDTjJ -+ZSgdgUCgYEA41aetBJwsKkF973gL54QCB5vFhRw3TdUgYhQgz04B5JGouGTSALy -u1XtO6il65Zf6FwFSzXiggYYxTKyP/zwL88CQAVA7rleyhoZrw2bD6R2RZLivRba -50rstltUbjevd96TagFY7i9gVHL9E6DKJH4unZfIM0Bl2IZQraqCR8MCgYEA4PzC -FfUwiLa5drN6OVWZZfwxOeMbQUsYVeq7pHyeuvIe0euhcCLabBqfVt0pxqf1hem+ -l2+PnSKtvbI9siwt6WvJCtB3e/3aHOA3d6Y9TYxoyJAK007mRlQbbgqLzG83tZH2 -twO2tjo+h1+nv5yjE7aF9ItszegwTWsupvR+Ei8CgYAy0nt6MCEnLTIbV0RWANT+ -q6cT3Y/5tFPc/Vdab4YmEypdYWZmk9olzSjSzHoDN8PLEz9PuAUiIjDJbPLyYR5k -4bdUDpicha5OKhWRz83Zal/SX+r2cLSRPmu6vKIcXbCJcKWt7g0uekLjvi0bhTeL -fvX23yavZnceN7Czkkm7twKBgEFTgrNHdykrDRzXLhT5ssm2+UAani5ONKm1t3gi -KyCS7rn7FevuYsdiz4M0Qk4JNLQGU6262dNBX3smBt32D/qnrj8ymo7o/WzG+bQH -E+OxcjdSA6KpVRl0kGZaL49Td7SDxkQLkwDEVqWN87IiNAOkSq7f0N7UnTnNdkVJ -1lVHAoGBANYgMoEj7gIJdch7hMdQcFfq9+4ntAAbsl3JFW+T9ChATn0XHAylP9ha -ZaGlRrC7vxcF06vMe0HXyH1XVK3J9186zliTa4oDjkQ0D5X7Ga7KktLXAmQTysUH -V3jwIQbAF6LqLUnGOq6rJzQxrWKvFt0mVDyuJzIJGSbnN/Sl5J6P ------END RSA PRIVATE KEY----- -EOH - IO.stub!(:read).and_return(@private_key) - end + Chef::REST::CookieJar.instance.clear + end + + after do + Chef::Log.logger = @original_chef_logger + end - it "should return the contents of the key file" do - File.stub!(:exists?).and_return(true) - File.stub!(:readable?).and_return(true) - @rest.load_signing_key("/tmp/keyfile.pem").should be(@private_key) + describe "calling an HTTP verb on a path or absolute URL" do + it "adds a relative URL to the base url it was initialized with" do + @rest.create_url("foo/bar/baz").should == URI.parse(@base_url + "/foo/bar/baz") end - it "should raise a Chef::Exceptions::PrivateKeyMissing exception if the key cannot be found" do - IO.stub!(:read).and_raise(IOError) - lambda { - @rest.load_signing_key("/tmp/keyfile.pem") - }.should raise_error(Chef::Exceptions::PrivateKeyMissing) + it "replaces the base URL when given an absolute URL" do + @rest.create_url("http://chef-rulez.example.com:9000").should == URI.parse("http://chef-rulez.example.com:9000") end - end - - describe "get_rest" do - it "should create a url from the path and base url" do - URI.should_receive(:parse).with("url/monkey") - @rest.stub!(:run_request) + it "makes a :GET request with the composed url object" do + @rest.should_receive(:api_request).with(:GET, @monkey_uri, {}) @rest.get_rest("monkey") end - - it "should call run_request :GET with the composed url object" do - URI.stub!(:parse).and_return(true) - @rest.should_receive(:run_request).with(:GET, true, {}, false, 10, false).and_return(true) - @rest.get_rest("monkey") - end - end - describe "delete_rest" do - it "should create a url from the path and base url" do - URI.should_receive(:parse).with("url/monkey") - @rest.stub!(:run_request) - @rest.delete_rest("monkey") + it "makes a :GET reqest for a streaming download with the composed url" do + @rest.should_receive(:streaming_request).with(@monkey_uri, {}) + @rest.get_rest("monkey", true) end - - it "should call run_request :DELETE with the composed url object" do - URI.stub!(:parse).and_return(true) - @rest.should_receive(:run_request).with(:DELETE, true, {}).and_return(true) + + it "makes a :DELETE request with the composed url object" do + @rest.should_receive(:api_request).with(:DELETE, @monkey_uri, {}) @rest.delete_rest("monkey") end - end - describe "post_rest" do - it "should create a url from the path and base url" do - URI.should_receive(:parse).with("url/monkey") - @rest.stub!(:run_request) - @rest.post_rest("monkey", "data") - end - - it "should call run_request :POST with the composed url object and data" do - URI.stub!(:parse).and_return(true) - @rest.should_receive(:run_request).with(:POST, true, {}, "data").and_return(true) + it "makes a :POST request with the composed url object and data" do + @rest.should_receive(:api_request).with(:POST, @monkey_uri, {}, "data") @rest.post_rest("monkey", "data") end - end - describe "put_rest" do - it "should create a url from the path and base url" do - URI.should_receive(:parse).with("url/monkey") - @rest.stub!(:run_request) - @rest.put_rest("monkey", "data") - end - - it "should call run_request :PUT with the composed url object and data" do - URI.stub!(:parse).and_return(true) - @rest.should_receive(:run_request).with(:PUT, true, {}, "data").and_return(true) + it "makes a :PUT request with the composed url object and data" do + @rest.should_receive(:api_request).with(:PUT, @monkey_uri, {}, "data") @rest.put_rest("monkey", "data") end end - describe Chef::REST, "run_request method" do - before(:each) do - @url_mock = mock("URI", :null_object => true) - @url_mock.stub!(:host).and_return("one") - @url_mock.stub!(:port).and_return("80") - @url_mock.stub!(:path).and_return("/") - @url_mock.stub!(:query).and_return("foo=bar") - @url_mock.stub!(:scheme).and_return("https") - @url_mock.stub!(:to_s).and_return("https://one:80/?foo=bar") - @http_response_mock = mock("Net::HTTPSuccess", :null_object => true) - @http_response_mock.stub!(:kind_of?).with(Net::HTTPSuccess).and_return(true) - @http_response_mock.stub!(:body).and_return("ninja") - @http_response_mock.stub!(:error!).and_return(true) - @http_response_mock.stub!(:header).and_return({ 'Content-Length' => "5" }) - @http_mock = mock("Net::HTTP", :null_object => true) - @http_mock.stub!(:verify_mode=).and_return(true) - @http_mock.stub!(:read_timeout=).and_return(true) - @http_mock.stub!(:use_ssl=).with(true).and_return(true) - @data_mock = mock("Data", :null_object => true) - @data_mock.stub!(:to_json).and_return('{ "one": "two" }') - @request_mock = mock("Request", :null_object => true) - @request_mock.stub!(:body=).and_return(true) - @request_mock.stub!(:method).and_return(true) - @request_mock.stub!(:path).and_return(true) - @http_mock.stub!(:request).and_return(@http_response_mock) - @tf_mock = mock(Tempfile, { :print => true, :close => true, :write => true }) - Tempfile.stub!(:new).with("chef-rest").and_return(@tf_mock) + + describe "when configured to authenticate to the Chef server" do + before do + @url = URI.parse("http://chef.example.com:4000") + Chef::Config[:node_name] = "webmonkey.example.com" + Chef::Config[:client_key] = CHEF_SPEC_DATA + "/ssl/private_key.pem" + @rest = Chef::REST.new(@url) end - - def do_run_request(method=:GET, data=false, limit=10, raw=false) - Net::HTTP.stub!(:new).and_return(@http_mock) - @rest.run_request(method, @url_mock, {}, data, limit, raw) + + it "configures itself to use the node_name and client_key in the config by default" do + @rest.client_name.should == "webmonkey.example.com" + @rest.signing_key_filename.should == CHEF_SPEC_DATA + "/ssl/private_key.pem" end - it "should always include the X-Chef-Version header" do - Net::HTTP::Get.should_receive(:new).with("/?foo=bar", - { 'Accept' => 'application/json', 'X-Chef-Version' => Chef::VERSION } - ).and_return(@request_mock) - do_run_request + it "provides access to the raw key data" do + @rest.signing_key.should == SIGNING_KEY_DOT_PEM end - - it "should raise an exception if the redirect limit is 0" do - lambda { @rest.run_request(:GET, "/", {}, false, 0)}.should raise_error(ArgumentError) + + it "does not error out when initialized without credentials" do + @rest = Chef::REST.new(@url, nil, nil) #should_not raise_error hides the bt from you, so screw it. + @rest.client_name.should be_nil + @rest.signing_key.should be_nil end - - it "should use SSL if the url starts with https" do - @url_mock.should_receive(:scheme).and_return("https") - @http_mock.should_receive(:use_ssl=).with(true).and_return(true) - do_run_request + + it "indicates that requests should not be signed when it has no credentials" do + @rest = Chef::REST.new(@url, nil, nil) + @rest.sign_requests?.should be_false end - - it "should set the OpenSSL Verify Mode to verify_none if requested" do - @http_mock.should_receive(:verify_mode=).and_return(true) - do_run_request + + end + + context "when making REST requests" do + before(:each) do + Chef::Config[:ssl_client_cert] = nil + Chef::Config[:ssl_client_key] = nil + @url = URI.parse("https://one:80/?foo=bar") + + @http_response = Net::HTTPSuccess.new("1.1", "200", "successful rest req") + @http_response.stub!(:read_body) + @http_response.stub!(:body).and_return("ninja") + @http_response.add_field("Content-Length", "5") + + @http_client = Net::HTTP.new(@url.host, @url.port) + Net::HTTP.stub!(:new).and_return(@http_client) + @http_client.stub!(:request).and_yield(@http_response).and_return(@http_response) end - - describe "with OpenSSL Verify Mode set to :verify peer" do - before(:each) do - Chef::Config[:ssl_verify_mode] = :verify_peer - @url_mock.should_receive(:scheme).and_return("https") + + describe "using the run_request API" do + it "should build a new HTTP GET request" do + request = Net::HTTP::Get.new(@url.path) + Net::HTTP::Get.should_receive(:new).with("/?foo=bar", + { 'Accept' => 'application/json', 'X-Chef-Version' => Chef::VERSION } + ).and_return(request) + @rest.run_request(:GET, @url, {}) end - after(:each) do - Chef::Config[:ssl_verify_mode] = :verify_none + it "should build a new HTTP POST request" do + request = Net::HTTP::Post.new(@url.path) + + Net::HTTP::Post.should_receive(:new).with("/?foo=bar", + { 'Accept' => 'application/json', "Content-Type" => 'application/json', 'X-Chef-Version' => Chef::VERSION } + ).and_return(request) + @rest.run_request(:POST, @url, {}, {:one=>:two}) + request.body.should == '{"one":"two"}' end - it "should set the OpenSSL Verify Mode to verify_peer if requested" do - @http_mock.should_receive(:verify_mode=).with(OpenSSL::SSL::VERIFY_PEER).and_return(true) - do_run_request + it "should build a new HTTP PUT request" do + request = Net::HTTP::Put.new(@url.path) + Net::HTTP::Put.should_receive(:new).with("/?foo=bar", + { 'Accept' => 'application/json', "Content-Type" => 'application/json', 'X-Chef-Version' => Chef::VERSION } + ).and_return(request) + @rest.run_request(:PUT, @url, {}, {:one=>:two}) + request.body.should == '{"one":"two"}' end - it "should set the CA path if that is set in the configuration" do - Chef::Config[:ssl_ca_path] = File.join(File.dirname(__FILE__), "..", "data", "ssl") - @http_mock.should_receive(:ca_path=).with(Chef::Config[:ssl_ca_path]).and_return(true) - do_run_request - Chef::Config[:ssl_ca_path] = nil + it "should build a new HTTP DELETE request" do + request = Net::HTTP::Delete.new(@url.path) + Net::HTTP::Delete.should_receive(:new).with("/?foo=bar", + { 'Accept' => 'application/json', 'X-Chef-Version' => Chef::VERSION } + ).and_return(request) + @rest.run_request(:DELETE, @url) end - it "should set the CA file if that is set in the configuration" do - Chef::Config[:ssl_ca_file] = File.join(File.dirname(__FILE__), "..", "data", "ssl", "5e707473.0") - @http_mock.should_receive(:ca_file=).with(Chef::Config[:ssl_ca_file]).and_return(true) - do_run_request - Chef::Config[:ssl_ca_file] = nil + it "should raise an error if the method is not GET/PUT/POST/DELETE" do + lambda { @rest.api_request(:MONKEY, @url) }.should raise_error(ArgumentError) end - end - describe "with a client SSL cert" do - before(:each) do - Chef::Config[:ssl_client_cert] = "/etc/chef/client-cert.pem" - Chef::Config[:ssl_client_key] = "/etc/chef/client-cert.key" - File.stub!(:exists?).with("/etc/chef/client-cert.pem").and_return(true) - File.stub!(:exists?).with("/etc/chef/client-cert.key").and_return(true) - File.stub!(:read).with("/etc/chef/client-cert.pem").and_return("monkey magic client") - File.stub!(:read).with("/etc/chef/client-cert.key").and_return("monkey magic key") - OpenSSL::X509::Certificate.stub!(:new).and_return("monkey magic client data") - OpenSSL::PKey::RSA.stub!(:new).and_return("monkey magic key data") + it "returns the response body when the response is successful but content-type is not JSON" do + @rest.run_request(:GET, @url).should == "ninja" end - it "should check that the client cert file exists" do - File.should_receive(:exists?).with("/etc/chef/client-cert.pem").and_return(true) - do_run_request + it "should call read_body without a block if the request is not raw" do + @http_response.should_receive(:read_body) + @rest.run_request(:GET, @url, {}, nil, false) end - it "should read the cert file" do - File.should_receive(:read).with("/etc/chef/client-cert.pem").and_return("monkey magic client") - do_run_request + it "should inflate the body as to an object if JSON is returned" do + @http_response.add_field("content-type", "application/json") + JSON.should_receive(:parse).with("ninja").and_return("ohai2u_success") + @rest.run_request(:GET, @url, {}).should == "ohai2u_success" end - it "should read the cert into OpenSSL" do - OpenSSL::X509::Certificate.should_receive(:new).and_return("monkey magic client data") - do_run_request + it "should call run_request again on a Redirect response" do + http_response = Net::HTTPFound.new("1.1", "302", "bob is taking care of that one for me today") + http_response.add_field("location", @url.path) + http_response.stub!(:read_body) + + @http_client.stub!(:request).and_yield(http_response).and_return(http_response) + lambda { @rest.run_request(:GET, @url) }.should raise_error(Chef::Exceptions::RedirectLimitExceeded) end - it "should set the cert" do - @http_mock.should_receive(:cert=).and_return(true) - do_run_request + it "should call run_request again on a Permanent Redirect response" do + http_response = Net::HTTPMovedPermanently.new("1.1", "301", "That's Bob's job") + http_response.add_field("location", @url.path) + http_response.stub!(:read_body) + @http_client.stub!(:request).and_yield(http_response).and_return(http_response) + lambda { @rest.run_request(:GET, @url) }.should raise_error(Chef::Exceptions::RedirectLimitExceeded) end - it "should read the key file" do - File.should_receive(:read).with("/etc/chef/client-cert.key").and_return("monkey magic key") - do_run_request + it "should show the JSON error message on an unsuccessful request" do + http_response = Net::HTTPServerError.new("1.1", "500", "drooling from inside of mouth") + http_response.add_field("content-type", "application/json") + http_response.stub!(:body).and_return('{ "error":[ "Ears get sore!", "Not even four" ] }') + http_response.stub!(:read_body) + @http_client.stub!(:request).and_yield(http_response).and_return(http_response) + lambda {@rest.run_request(:GET, @url)}.should raise_error(Net::HTTPFatalError) + @log_stringio.string.should match(Regexp.escape('WARN -- : HTTP Request Returned 500 drooling from inside of mouth: Ears get sore!, Not even four')) end - it "should read the key into OpenSSL" do - OpenSSL::PKey::RSA.should_receive(:new).and_return("monkey magic key data") - do_run_request + it "should raise an exception on an unsuccessful request" do + @http_response = Net::HTTPServerError.new("1.1", "500", "drooling from inside of mouth") + http_response = Net::HTTPServerError.new("1.1", "500", "drooling from inside of mouth") + http_response.stub!(:read_body) + @http_client.stub!(:request).and_yield(http_response).and_return(http_response) + lambda {@rest.run_request(:GET, @url)}.should raise_error(Net::HTTPFatalError) end - it "should set the key" do - @http_mock.should_receive(:key=).and_return(true) - do_run_request + describe "streaming downloads to a tempfile" do + before do + @tempfile = Tempfile.open("chef-rspec-rest_spec-line-#{__LINE__}--") + Tempfile.stub!(:new).with("chef-rest").and_return(@tempfile) + Tempfile.stub!(:open).and_return(@tempfile) + end + + after do + @tempfile.rspec_reset + @tempfile.close! + end + + it "should build a new HTTP GET request without the application/json accept header" do + Net::HTTP::Get.should_receive(:new).with("/?foo=bar", {'X-Chef-Version' => Chef::VERSION}).and_return(@request_mock) + @rest.run_request(:GET, @url, {}, false, nil, true) + end + + it "should create a tempfile for the output of a raw request" do + @rest.run_request(:GET, @url, {}, false, nil, true).should equal(@tempfile) + end + + it "should read the body of the response in chunks on a raw request" do + @http_response.should_receive(:read_body).and_return(true) + @rest.run_request(:GET, @url, {}, false, nil, true) + end + + it "should populate the tempfile with the value of the raw request" do + @http_response_mock.stub!(:read_body).and_yield("ninja") + @tempfile.should_receive(:write).with("ninja").once.and_return(true) + @rest.run_request(:GET, @url, {}, false, nil, true) + end + + it "should close the tempfile if we're doing a raw request" do + @tempfile.should_receive(:close).once.and_return(true) + @rest.run_request(:GET, @url, {}, false, nil, true) + end + + it "should not raise a divide by zero exception if the size is 0" do + @http_response_mock.stub!(:header).and_return({ 'Content-Length' => "5" }) + @http_response_mock.stub!(:read_body).and_yield('') + lambda { @rest.run_request(:GET, @url, {}, false, nil, true) }.should_not raise_error(ZeroDivisionError) + end + + it "should not raise a divide by zero exception if the Content-Length is 0" do + @http_response_mock.stub!(:header).and_return({ 'Content-Length' => "0" }) + @http_response_mock.stub!(:read_body).and_yield("ninja") + lambda { @rest.run_request(:GET, @url, {}, false, nil, true) }.should_not raise_error(ZeroDivisionError) + end + end end - it "should set a read timeout based on the rest_timeout config option" do - Chef::Config[:rest_timeout] = 10 - @http_mock.should_receive(:read_timeout=).with(10).and_return(true) - do_run_request - end - - it "should set the cookie for this request if one exists for the given host:port" do - @rest.cookies = { "#{@url_mock.host}:#{@url_mock.port}" => "cookie monster" } - Net::HTTP::Get.should_receive(:new).with("/?foo=bar", - { 'Accept' => 'application/json', 'X-Chef-Version' => Chef::VERSION, 'Cookie' => 'cookie monster' } - ).and_return(@request_mock) - do_run_request - @rest.cookies = Hash.new - end - - it "should build a new HTTP GET request" do - Net::HTTP::Get.should_receive(:new).with("/?foo=bar", - { 'Accept' => 'application/json', 'X-Chef-Version' => Chef::VERSION } - ).and_return(@request_mock) - do_run_request - end - - it "should build a new HTTP POST request" do - Net::HTTP::Post.should_receive(:new).with("/?foo=bar", - { 'Accept' => 'application/json', "Content-Type" => 'application/json', 'X-Chef-Version' => Chef::VERSION } - ).and_return(@request_mock) - do_run_request(:POST, @data_mock) - end - - it "should build a new HTTP PUT request" do - Net::HTTP::Put.should_receive(:new).with("/?foo=bar", - { 'Accept' => 'application/json', "Content-Type" => 'application/json', 'X-Chef-Version' => Chef::VERSION } - ).and_return(@request_mock) - do_run_request(:PUT, @data_mock) - end - - it "should build a new HTTP DELETE request" do - Net::HTTP::Delete.should_receive(:new).with("/?foo=bar", - { 'Accept' => 'application/json', 'X-Chef-Version' => Chef::VERSION } - ).and_return(@request_mock) - do_run_request(:DELETE) - end - - it "should raise an error if the method is not GET/PUT/POST/DELETE" do - lambda { do_run_request(:MONKEY) }.should raise_error(ArgumentError) - end - - it "should run an http request" do - @http_mock.should_receive(:request).and_return(@http_response_mock) - do_run_request - end - - it "should return the body of the response on success" do - do_run_request.should eql("ninja") - end - - it "should inflate the body as to an object if JSON is returned" do - @http_response_mock.stub!(:[]).with('content-type').and_return("application/json") - JSON.should_receive(:parse).with("ninja").and_return(true) - do_run_request - end - - it "should call run_request again on a Redirect response" do - @http_response_mock.stub!(:kind_of?).with(Net::HTTPSuccess).and_return(false) - @http_response_mock.stub!(:kind_of?).with(Net::HTTPFound).and_return(true) - @http_response_mock.stub!(:[]).with('location').and_return(@url_mock.path) - lambda { do_run_request(method=:GET, data=false, limit=1) }.should raise_error(ArgumentError) - end + describe "as JSON API requests" do + it "should always include the X-Chef-Version header" do + Net::HTTP::Get.should_receive(:new).with("/?foo=bar", + { 'Accept' => 'application/json', 'X-Chef-Version' => Chef::VERSION } + ).and_return(@request_mock) + @rest.api_request(:GET, @url, {}) + end - it "should call run_request again on a Permanent Redirect response" do - @http_response_mock.stub!(:kind_of?).with(Net::HTTPSuccess).and_return(false) - @http_response_mock.stub!(:kind_of?).with(Net::HTTPFound).and_return(false) - @http_response_mock.stub!(:kind_of?).with(Net::HTTPMovedPermanently).and_return(true) - @http_response_mock.stub!(:[]).with('location').and_return(@url_mock.path) - lambda { do_run_request(method=:GET, data=false, limit=1) }.should raise_error(ArgumentError) - end + it "should set the cookie for this request if one exists for the given host:port" do + Chef::REST::CookieJar.instance["#{@url.host}:#{@url.port}"] = "cookie monster" + Net::HTTP::Get.should_receive(:new).with("/?foo=bar", + { 'Accept' => 'application/json', 'X-Chef-Version' => Chef::VERSION, 'Cookie' => 'cookie monster' } + ).and_return(@request_mock) + @rest.api_request(:GET, @url, {}) + end - it "should show the JSON error message on an unsuccessful request" do - @http_response_mock.stub!(:kind_of?).with(Net::HTTPSuccess).and_return(false) - @http_response_mock.stub!(:kind_of?).with(Net::HTTPFound).and_return(false) - @http_response_mock.stub!(:kind_of?).with(Net::HTTPMovedPermanently).and_return(false) - @http_response_mock.stub!(:[]).with('content-type').and_return('application/json') - @http_response_mock.stub!(:body).and_return('{ "error":[ "Ears get sore!", "Not even four" ] }') - @http_response_mock.stub!(:code).and_return(500) - @http_response_mock.stub!(:message).and_return('Server Error') - ## BUGBUG - this should absolutely be working, but it.. isn't. - #Chef::Log.should_receive(:warn).with("HTTP Request Returned 500 Server Error: Ears get sore!, Not even four") - @http_response_mock.should_receive(:error!) - do_run_request - end - - it "should raise an exception on an unsuccessful request" do - @http_response_mock.stub!(:kind_of?).with(Net::HTTPSuccess).and_return(false) - @http_response_mock.stub!(:kind_of?).with(Net::HTTPFound).and_return(false) - @http_response_mock.stub!(:kind_of?).with(Net::HTTPMovedPermanently).and_return(false) - @http_response_mock.should_receive(:error!) - do_run_request - end - - it "should build a new HTTP GET request without the application/json accept header for raw reqs" do - Net::HTTP::Get.should_receive(:new).with("/?foo=bar", {'X-Chef-Version' => Chef::VERSION}).and_return(@request_mock) - do_run_request(:GET, false, 10, true) + it "should build a new HTTP GET request" do + Net::HTTP::Get.should_receive(:new).with("/?foo=bar", + { 'Accept' => 'application/json', 'X-Chef-Version' => Chef::VERSION } + ).and_return(@request_mock) + @rest.api_request(:GET, @url, {}) + end + + it "should build a new HTTP POST request" do + request = Net::HTTP::Post.new(@url.path) + + Net::HTTP::Post.should_receive(:new).with("/?foo=bar", + { 'Accept' => 'application/json', "Content-Type" => 'application/json', 'X-Chef-Version' => Chef::VERSION } + ).and_return(request) + @rest.api_request(:POST, @url, {}, {:one=>:two}) + request.body.should == '{"one":"two"}' + end + + it "should build a new HTTP PUT request" do + request = Net::HTTP::Put.new(@url.path) + Net::HTTP::Put.should_receive(:new).with("/?foo=bar", + { 'Accept' => 'application/json', "Content-Type" => 'application/json', 'X-Chef-Version' => Chef::VERSION } + ).and_return(request) + @rest.api_request(:PUT, @url, {}, {:one=>:two}) + request.body.should == '{"one":"two"}' + end + + it "should build a new HTTP DELETE request" do + Net::HTTP::Delete.should_receive(:new).with("/?foo=bar", + { 'Accept' => 'application/json', 'X-Chef-Version' => Chef::VERSION } + ).and_return(@request_mock) + @rest.api_request(:DELETE, @url) + end + + it "should raise an error if the method is not GET/PUT/POST/DELETE" do + lambda { @rest.api_request(:MONKEY, @url) }.should raise_error(ArgumentError) + end + + it "returns nil when the response is successful but content-type is not JSON" do + @rest.api_request(:GET, @url).should == "ninja" + end + + it "should inflate the body as to an object if JSON is returned" do + @http_response.add_field('content-type', "application/json") + @http_response.stub!(:body).and_return('{"ohai2u":"json_api"}') + @rest.api_request(:GET, @url, {}).should == {"ohai2u"=>"json_api"} + end + + it "should call run_request again on a Redirect response" do + http_response = Net::HTTPFound.new("1.1", "302", "bob is taking care of that one for me today") + http_response.add_field("location", @url.path) + http_response.stub!(:read_body) + + @http_client.stub!(:request).and_yield(http_response).and_return(http_response) + + lambda { @rest.api_request(:GET, @url) }.should raise_error(Chef::Exceptions::RedirectLimitExceeded) + end + + it "should call run_request again on a Permanent Redirect response" do + http_response = Net::HTTPMovedPermanently.new("1.1", "301", "That's Bob's job") + http_response.add_field("location", @url.path) + http_response.stub!(:read_body) + @http_client.stub!(:request).and_yield(http_response).and_return(http_response) + + lambda { @rest.api_request(:GET, @url) }.should raise_error(Chef::Exceptions::RedirectLimitExceeded) + end + + it "should show the JSON error message on an unsuccessful request" do + http_response = Net::HTTPServerError.new("1.1", "500", "drooling from inside of mouth") + http_response.add_field("content-type", "application/json") + http_response.stub!(:body).and_return('{ "error":[ "Ears get sore!", "Not even four" ] }') + http_response.stub!(:read_body) + @http_client.stub!(:request).and_yield(http_response).and_return(http_response) + + lambda {@rest.run_request(:GET, @url)}.should raise_error(Net::HTTPFatalError) + @log_stringio.string.should match(Regexp.escape('WARN -- : HTTP Request Returned 500 drooling from inside of mouth: Ears get sore!, Not even four')) + end + + it "should raise an exception on an unsuccessful request" do + http_response = Net::HTTPServerError.new("1.1", "500", "drooling from inside of mouth") + http_response.stub!(:body) + http_response.stub!(:read_body) + @http_client.stub!(:request).and_yield(http_response).and_return(http_response) + lambda {@rest.api_request(:GET, @url)}.should raise_error(Net::HTTPFatalError) + end end - - it "should create a tempfile for the output of a raw request" do - @http_mock.stub!(:request).and_yield(@http_response_mock).and_return(@http_response_mock) - Tempfile.should_receive(:new).with("chef-rest").and_return(@tf_mock) - do_run_request(:GET, false, 10, true).should eql(@tf_mock) + + context "when streaming downloads to a tempfile" do + before do + @tempfile = Tempfile.open("chef-rspec-rest_spec-line-#{__LINE__}--") + Tempfile.stub!(:new).with("chef-rest").and_return(@tempfile) + @http_response = Net::HTTPSuccess.new("1.1",200, "it-works") + @http_response.stub!(:read_body) + @http_client.stub!(:request).and_yield(@http_response).and_return(@http_response) + end + + after do + @tempfile.rspec_reset + @tempfile.close! + end + + it " build a new HTTP GET request without the application/json accept header" do + Net::HTTP::Get.should_receive(:new).with("/?foo=bar", {'X-Chef-Version' => Chef::VERSION}).and_return(@request_mock) + @rest.streaming_request(@url, {}) + end + + it "returns a tempfile containing the streamed response body" do + @rest.streaming_request(@url, {}).should equal(@tempfile) + end + + it "writes the response body to a tempfile" do + @http_response.stub!(:read_body).and_yield("real").and_yield("ultimate").and_yield("power") + @rest.streaming_request(@url, {}) + IO.read(@tempfile.path).chomp.should == "realultimatepower" + end + + it "closes the tempfile" do + @rest.streaming_request(@url, {}) + @tempfile.should be_closed + end + + it "yields the tempfile containing the streamed response body and then unlinks it when given a block" do + @http_response.stub!(:read_body).and_yield("real").and_yield("ultimate").and_yield("power") + tempfile_path = nil + @rest.streaming_request(@url, {}) do |tempfile| + tempfile_path = tempfile.path + File.exist?(tempfile.path).should be_true + IO.read(@tempfile.path).chomp.should == "realultimatepower" + end + File.exist?(tempfile_path).should be_false + end + + it "does not raise a divide by zero exception if the content's actual size is 0" do + @http_response.add_field('Content-Length', "5") + @http_response.stub!(:read_body).and_yield('') + lambda { @rest.streaming_request(@url, {}) }.should_not raise_error(ZeroDivisionError) + end + + it "does not raise a divide by zero exception when the Content-Length is 0" do + @http_response.add_field('Content-Length', "0") + @http_response.stub!(:read_body).and_yield("ninja") + lambda { @rest.streaming_request(@url, {}) }.should_not raise_error(ZeroDivisionError) + end + + it "fetches a file and yields the tempfile it is streamed to" do + @http_response.stub!(:read_body).and_yield("real").and_yield("ultimate").and_yield("power") + tempfile_path = nil + @rest.fetch("cookbooks/a_cookbook") do |tempfile| + tempfile_path = tempfile.path + IO.read(@tempfile.path).chomp.should == "realultimatepower" + end + File.exist?(tempfile_path).should be_false + end + + it "closes and unlinks the tempfile if there is an error while streaming the content to the tempfile" do + @tempfile.stub!(:write).and_raise(IOError) + @rest.fetch("cookbooks/a_cookbook") {|tmpfile| "shouldn't get here"} + @tempfile.path.should be_nil + end + + it "closes and unlinks the tempfile when the response is a redirect" do + Tempfile.rspec_reset + tempfile = mock("die", :path => "/tmp/ragefist", :close => true) + tempfile.should_receive(:close!).at_least(2).times + Tempfile.stub!(:new).with("chef-rest").and_return(tempfile) + + http_response = Net::HTTPFound.new("1.1", "302", "bob is taking care of that one for me today") + http_response.add_field("location", @url.path) + http_response.stub!(:read_body) + + @http_client.stub!(:request).and_yield(http_response).and_yield(@http_response).and_return(http_response, @http_response) + @rest.fetch("cookbooks/a_cookbook") {|tmpfile| "shouldn't get here"} + end + + it "passes the original block to the redirected request" do + http_response = Net::HTTPFound.new("1.1", "302", "bob is taking care of that one for me today") + http_response.add_field("location","/that-thing-is-here-now") + http_response.stub!(:read_body) + + block_called = false + @http_client.stub!(:request).and_yield(@http_response).and_return(http_response, @http_response) + @rest.fetch("cookbooks/a_cookbook") do |tmpfile| + block_called = true + end + block_called.should be_true + end end - - it "should read the body of the response in chunks on a raw request" do - @http_mock.stub!(:request).and_yield(@http_response_mock).and_return(@http_response_mock) - @http_response_mock.should_receive(:read_body).and_return(true) - do_run_request(:GET, false, 10, true) + end + + context "when following redirects" do + before do + Chef::Config[:node_name] = "webmonkey.example.com" + Chef::Config[:client_key] = CHEF_SPEC_DATA + "/ssl/private_key.pem" + @rest = Chef::REST.new(@url) end - - it "should populate the tempfile with the value of the raw request" do - @http_mock.stub!(:request).and_yield(@http_response_mock).and_return(@http_response_mock) - @http_response_mock.stub!(:read_body).and_yield("ninja") - @tf_mock.should_receive(:write, "ninja").once.and_return(true) - do_run_request(:GET, false, 10, true) + + it "raises a RedirectLimitExceeded when redirected more than 10 times" do + redirected = lambda {@rest.follow_redirect { redirected.call }} + lambda {redirected.call}.should raise_error(Chef::Exceptions::RedirectLimitExceeded) end - - it "should close the tempfile if we're doing a raw request" do - @http_mock.stub!(:request).and_yield(@http_response_mock).and_return(@http_response_mock) - @tf_mock.should_receive(:close).once.and_return(true) - do_run_request(:GET, false, 10, true) + + it "does not count redirects from previous calls against the redirect limit" do + total_redirects = 0 + redirected = lambda do + @rest.follow_redirect do + total_redirects += 1 + redirected.call unless total_redirects >= 9 + end + end + lambda {redirected.call}.should_not raise_error(Chef::Exceptions::RedirectLimitExceeded) + total_redirects = 0 + lambda {redirected.call}.should_not raise_error(Chef::Exceptions::RedirectLimitExceeded) end - - it "should not raise a divide by zero exception if the size is 0" do - @http_mock.stub!(:request).and_yield(@http_response_mock).and_return(@http_response_mock) - @http_response_mock.stub!(:header).and_return({ 'Content-Length' => "5" }) - @http_response_mock.stub!(:read_body).and_yield('') - lambda { do_run_request(:GET, false, 10, true) }.should_not raise_error(ZeroDivisionError) + + it "does not sign the redirected request when sign_on_redirect is false" do + @rest.sign_on_redirect = false + @rest.follow_redirect { @rest.sign_requests?.should be_false } end - - it "should not raise a divide by zero exception if the Content-Length is 0" do - @http_mock.stub!(:request).and_yield(@http_response_mock).and_return(@http_response_mock) - @http_response_mock.stub!(:header).and_return({ 'Content-Length' => "0" }) - @http_response_mock.stub!(:read_body).and_yield("ninja") - lambda { do_run_request(:GET, false, 10, true) }.should_not raise_error(ZeroDivisionError) + + it "resets sign_requests to the original value after following an unsigned redirect" do + @rest.sign_on_redirect = false + @rest.sign_requests?.should be_true + + @rest.follow_redirect { @rest.sign_requests?.should be_false } + @rest.sign_requests?.should be_true end - - it "should call read_body without a block if the request is not raw" do - @http_mock.stub!(:request).and_yield(@http_response_mock).and_return(@http_response_mock) - @http_response_mock.should_receive(:read_body) - do_run_request(:GET, false, 10, false) + + it "configures the redirect limit" do + total_redirects = 0 + redirected = lambda do + @rest.follow_redirect do + total_redirects += 1 + redirected.call unless total_redirects >= 9 + end + end + lambda {redirected.call}.should_not raise_error(Chef::Exceptions::RedirectLimitExceeded) + + total_redirects = 0 + @rest.redirect_limit = 3 + lambda {redirected.call}.should raise_error(Chef::Exceptions::RedirectLimitExceeded) end end - end - diff --git a/chef/spec/unit/util/file_edit_spec.rb b/chef/spec/unit/util/file_edit_spec.rb index 24d6503fc2..8f4075f6a7 100644 --- a/chef/spec/unit/util/file_edit_spec.rb +++ b/chef/spec/unit/util/file_edit_spec.rb @@ -16,12 +16,18 @@ # limitations under the License. # +require File.expand_path(File.dirname(__FILE__) + "/../../spec_helper") -require File.join(File.dirname(__FILE__), '..', '..', "spec_helper") +module Home + PATH = File.expand_path(File.dirname(__FILE__)) + DATA = File.join(PATH, "..", "..", "data", "fileedit") + HOSTS = File.join(DATA, "hosts") + HOSTS_OLD = File.join(DATA, "hosts.old") +end describe Chef::Util::FileEdit, "initialiize" do it "should create a new Chef::Util::FileEdit object" do - Chef::Util::FileEdit.new("./spec/data/fileedit/hosts").should be_kind_of(Chef::Util::FileEdit) + Chef::Util::FileEdit.new(Home::HOSTS).should be_kind_of(Chef::Util::FileEdit) end it "should throw an exception if the input file does not exist" do @@ -29,7 +35,7 @@ describe Chef::Util::FileEdit, "initialiize" do end it "should throw an exception if the input file is blank" do - lambda{Chef::Util::FileEdit.new("./spec/data/fileedit/blank")}.should raise_error + lambda{Chef::Util::FileEdit.new(Home::DATA + "/blank")}.should raise_error end end @@ -37,17 +43,17 @@ end describe Chef::Util::FileEdit, "search_file_replace" do it "should accept regex passed in as a string (not Regexp object) and replace the match if there is one" do - helper_method("./spec/data/fileedit/hosts", "localhost", true) + helper_method(Home::HOSTS, "localhost", true) end it "should accept regex passed in as a Regexp object and replace the match if there is one" do - helper_method("./spec/data/fileedit/hosts", /localhost/, true) + helper_method(Home::HOSTS, /localhost/, true) end it "should do nothing if there isn't a match" do - helper_method("./spec/data/fileedit/hosts", /pattern/, false) + helper_method(Home::HOSTS, /pattern/, false) end @@ -59,8 +65,8 @@ describe Chef::Util::FileEdit, "search_file_replace" do if value == true newfile = File.new(filename).readlines newfile[0].should match(/replacement/) - File.delete("./spec/data/fileedit/hosts") - File.rename("./spec/data/fileedit/hosts.old", "./spec/data/fileedit/hosts") + File.delete(Home::HOSTS) + File.rename(Home::HOSTS_OLD, Home::HOSTS) end end @@ -69,53 +75,53 @@ end describe Chef::Util::FileEdit, "search_file_replace_line" do it "should search for match and replace the whole line" do - fedit = Chef::Util::FileEdit.new("./spec/data/fileedit/hosts") + fedit = Chef::Util::FileEdit.new(Home::HOSTS) fedit.search_file_replace_line(/localhost/, "replacement line") fedit.write_file - newfile = File.new("./spec/data/fileedit/hosts").readlines + newfile = File.new(Home::HOSTS).readlines newfile[0].should match(/replacement/) newfile[0].should_not match(/127/) - File.delete("./spec/data/fileedit/hosts") - File.rename("./spec/data/fileedit/hosts.old", "./spec/data/fileedit/hosts") + File.delete(Home::HOSTS) + File.rename(Home::HOSTS_OLD, Home::HOSTS) end end describe Chef::Util::FileEdit, "search_file_delete" do it "should search for match and delete the match" do - fedit = Chef::Util::FileEdit.new("./spec/data/fileedit/hosts") + fedit = Chef::Util::FileEdit.new(Home::HOSTS) fedit.search_file_delete(/localhost/) fedit.write_file - newfile = File.new("./spec/data/fileedit/hosts").readlines + newfile = File.new(Home::HOSTS).readlines newfile[0].should_not match(/localhost/) newfile[0].should match(/127/) - File.delete("./spec/data/fileedit/hosts") - File.rename("./spec/data/fileedit/hosts.old", "./spec/data/fileedit/hosts") + File.delete(Home::HOSTS) + File.rename(Home::HOSTS_OLD, Home::HOSTS) end end describe Chef::Util::FileEdit, "search_file_delete_line" do it "should search for match and delete the matching line" do - fedit = Chef::Util::FileEdit.new("./spec/data/fileedit/hosts") + fedit = Chef::Util::FileEdit.new(Home::HOSTS) fedit.search_file_delete_line(/localhost/) fedit.write_file - newfile = File.new("./spec/data/fileedit/hosts").readlines + newfile = File.new(Home::HOSTS).readlines newfile[0].should_not match(/localhost/) newfile[0].should match(/broadcasthost/) - File.delete("./spec/data/fileedit/hosts") - File.rename("./spec/data/fileedit/hosts.old", "./spec/data/fileedit/hosts") + File.delete(Home::HOSTS) + File.rename(Home::HOSTS_OLD, Home::HOSTS) end end describe Chef::Util::FileEdit, "insert_line_after_match" do it "should search for match and insert the given line after the matching line" do - fedit = Chef::Util::FileEdit.new("./spec/data/fileedit/hosts") + fedit = Chef::Util::FileEdit.new(Home::HOSTS) fedit.insert_line_after_match(/localhost/, "new line inserted") fedit.write_file - newfile = File.new("./spec/data/fileedit/hosts").readlines + newfile = File.new(Home::HOSTS).readlines newfile[1].should match(/new/) - File.delete("./spec/data/fileedit/hosts") - File.rename("./spec/data/fileedit/hosts.old", "./spec/data/fileedit/hosts") + File.delete(Home::HOSTS) + File.rename(Home::HOSTS_OLD, Home::HOSTS) end end diff --git a/features/api/clients/update_client_api.feature b/features/api/clients/update_client_api.feature index ef647f858e..3e7703a8f9 100644 --- a/features/api/clients/update_client_api.feature +++ b/features/api/clients/update_client_api.feature @@ -25,4 +25,15 @@ Feature: Update a client And a 'client' named 'isis_update' When I 'PUT' the 'client' to the path '/clients/isis' Then I should get a '401 "Unauthorized"' exception + + @privilege_escalation + Scenario: Non-admin clients cannot update themselves + Given I am a non admin client + When I edit the 'not_admin' client + And I set 'admin' to true + And I save the client + Then I should get a '401 "Unauthorized"' exception + + + diff --git a/features/chef-client/cookbook_sync.feature b/features/chef-client/cookbook_sync.feature index cb4131546b..68e7e2be67 100644 --- a/features/chef-client/cookbook_sync.feature +++ b/features/chef-client/cookbook_sync.feature @@ -39,5 +39,5 @@ Feature: Synchronize cookbooks from the server Given it includes no recipes When I run the chef-client with '-l info' Then the run should exit '0' - And 'stdout' should have 'INFO: Removing cookbooks/synchronize_deps/recipes/default.rb from the cache; it's cookbook is no longer needed on this client.' + And 'stdout' should have 'INFO: Removing cookbooks/synchronize_deps/recipes/default.rb from the cache; its cookbook is no longer needed on this client.' diff --git a/features/chef-client/run_client.feature b/features/chef-client/run_client.feature new file mode 100644 index 0000000000..550a5d7ef8 --- /dev/null +++ b/features/chef-client/run_client.feature @@ -0,0 +1,13 @@ +@client +Feature: Run chef-client + In order to ensure a system is always correctly configured + As an Administrator + I want to run the chef-client + + @empty_run_list + Scenario: Run chef-client with an empty runlist should get a log warning the node has an empty run list + Given a validated node with an empty runlist + When I run the chef-client with '-l debug' + Then the run should exit '0' + And 'stdout' should have 'has an empty run list.' +
\ No newline at end of file diff --git a/features/chef-client/run_solo.feature b/features/chef-client/run_solo.feature new file mode 100644 index 0000000000..0073833c94 --- /dev/null +++ b/features/chef-client/run_solo.feature @@ -0,0 +1,11 @@ +@client @client_run_solo +Feature: Run chef-solo + In order to ensure a system is always correctly configured without chef-server + As an Administrator + I want to run the chef-solo + + Scenario: Run chef-solo without cookbooks should get error + When I run chef-solo without cookbooks + Then the run should exit '1' + And 'stdout' should have 'FATAL: No cookbook found' + diff --git a/features/cookbooks/lightweight_resources_and_providers.feature b/features/cookbooks/lightweight_resources_and_providers.feature index a45a03c278..8b5454f6e3 100644 --- a/features/cookbooks/lightweight_resources_and_providers.feature +++ b/features/cookbooks/lightweight_resources_and_providers.feature @@ -27,7 +27,7 @@ Feature: Light-weight resources and providers Then the run should exit '0' And a file named 'lwrp_touch_file.txt' should exist - @client @api + @client @lwrp_api Scenario Outline: Chef-client handles light-weight resources and providers Given a validated node And it includes the recipe 'lwrp::<recipe>' @@ -47,7 +47,7 @@ Feature: Light-weight resources and providers | provider_is_a_class | Provider is a class | | provider_is_omitted | P=Chef::Provider::LwrpProviderIsOmitted, R=Chef::Resource::LwrpProviderIsOmitted | - @client @api + @client @lwrp_api Scenario: Chef-client properly handles providers that invoke resources in their action definitions Given a validated node And it includes the recipe 'lwrp::provider_invokes_resource' diff --git a/features/steps/fixture_steps.rb b/features/steps/fixture_steps.rb index 9613e4a72f..b7131f9fb5 100644 --- a/features/steps/fixture_steps.rb +++ b/features/steps/fixture_steps.rb @@ -201,6 +201,14 @@ def get_fixture(stash_name, stash_key) end end +Given "I am a non admin client" do + r = Chef::REST.new(Chef::Config[:registration_url], Chef::Config[:validation_client_name], Chef::Config[:validation_key]) + r.register("not_admin", "#{tmpdir}/not_admin.pem") + c = Chef::ApiClient.cdb_load("not_admin") + c.cdb_save + @rest = Chef::REST.new(Chef::Config[:registration_url], 'not_admin', "#{tmpdir}/not_admin.pem") +end + Given /^an? '(.+)' named '(.+)'$/ do |stash_name, stash_key| # BUGBUG: I need to reference fixtures individually, but the fixtures, as written, store under the type, not the fixture's identifier and I don't currently have time to re-write the tests diff --git a/features/steps/node_steps.rb b/features/steps/node_steps.rb index b51d664e8c..7a74450a84 100644 --- a/features/steps/node_steps.rb +++ b/features/steps/node_steps.rb @@ -26,6 +26,13 @@ Given /^a validated node$/ do client.node.recipes << "integration_setup" end +Given /^a validated node with an empty runlist$/ do + client.determine_node_name + client.register + client.build_node +end + + Given /^it includes the recipe '(.+)'$/ do |recipe| self.recipe = recipe client.node.recipes << recipe diff --git a/features/steps/request_steps.rb b/features/steps/request_steps.rb index e71e75a3e0..ade12b854b 100644 --- a/features/steps/request_steps.rb +++ b/features/steps/request_steps.rb @@ -61,6 +61,23 @@ When /^I authenticate as '(.+)'$/ do |reg| end end +When "I edit the '$not_admin' client" do |client| + @object_to_edit = Chef::ApiClient.cdb_load("not_admin") +end + +When "I set '$property' to true" do |property| + @object_to_edit.send(property.to_sym, true) +end + +When "I save the client" do + begin + @rest.put_rest "clients/#{@object_to_edit.name}", @object_to_edit + rescue Exception => e + @exception = e + end +end + + #When /^I dump the contents of the search index$/ do # Given "I dump the contents of the search index" #end diff --git a/features/steps/run_solo.rb b/features/steps/run_solo.rb index b2b244cfb6..e1fea20253 100644 --- a/features/steps/run_solo.rb +++ b/features/steps/run_solo.rb @@ -40,3 +40,39 @@ When /^I run chef-solo with the '(.+)' recipe$/ do |recipe_name| print_output if ENV['LOG_LEVEL'] == 'debug' end + + +# This is kind of a crazy-ass setup, but it works. +When /^I run chef-solo without cookbooks$/ do + + # Set up the cache dir. + cache_dir = "#{tmpdir}/chef-solo-cache-features" + system("mkdir -p #{cache_dir}") + cleanup_dirs << cache_dir + + # Empty Cookbook dir + system("mkdir #{cache_dir}/cookbooks") + + # Config file + config_file = "#{tmpdir}/chef-solo-config-features.rb" + File.open(config_file, "w") do |fp| + fp.write("cookbook_path \"#{cache_dir}/cookbooks\"\n") + fp.write("file_cache_path \"#{cache_dir}/cookbooks\"\n") + end + cleanup_files << config_file + + binary_path = File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'chef', 'bin', 'chef-solo')) + command = "#{binary_path} -c #{config_file}" + command += " -l debug" if ENV['LOG_LEVEL'] == 'debug' + + # Run it + puts "Running solo: #{command}" if ENV['LOG_LEVEL'] == 'debug' + + status = Chef::Mixin::Command.popen4(command) do |p, i, o, e| + @stdout = o.gets(nil) + @stderr = o.gets(nil) + end + @status = status + + print_output if ENV['LOG_LEVEL'] == 'debug' +end
\ No newline at end of file diff --git a/features/support/env.rb b/features/support/env.rb index 4eed326f48..17a3145ff3 100644 --- a/features/support/env.rb +++ b/features/support/env.rb @@ -161,8 +161,9 @@ Before do system("mkdir -p #{tmpdir}") system("cp -r #{File.join(Dir.tmpdir, "validation.pem")} #{File.join(tmpdir, "validation.pem")}") system("cp -r #{File.join(Dir.tmpdir, "webui.pem")} #{File.join(tmpdir, "webui.pem")}") - Chef::CouchDB.new(Chef::Config[:couchdb_url], "chef_integration").create_db c = Chef::REST.new(Chef::Config[:couchdb_url], nil, nil) + c.delete_rest("chef_integration/") rescue nil + Chef::CouchDB.new(Chef::Config[:couchdb_url], "chef_integration").create_db c.post_rest("_replicate", { "source" => "#{Chef::Config[:couchdb_url]}/chef_integration_safe", "target" => "#{Chef::Config[:couchdb_url]}/chef_integration" @@ -170,8 +171,6 @@ Before do end After do - r = Chef::REST.new(Chef::Config[:couchdb_url], nil, nil) - r.delete_rest("chef_integration/") s = Chef::Solr.new s.solr_delete_by_query("*:*") s.solr_commit diff --git a/features/support/packages.rb b/features/support/packages.rb index 90272e543c..09e3a851c5 100644 --- a/features/support/packages.rb +++ b/features/support/packages.rb @@ -5,7 +5,7 @@ def package_system_available?(name) when 'MacPorts' uname = `uname` port = `which port` - (uname =~ /Darwin/ and !port.match(/not found/)) + (uname =~ /Darwin/ && !port.match(/not found/) && ::File.directory?('/opt')) else false end |