diff options
190 files changed, 5486 insertions, 555 deletions
@@ -1,5 +1,34 @@ Fri Sep 4 7:25:00 NZST 2009 +Release Notes - Chef - Version 0.7.12 + +** Bug + * [CHEF-293] - Chef breaks on systems with non-English Locales + * [CHEF-501] - Fails to follow notification chains + * [CHEF-534] - remove execute permissions from javascripts, images, etc. + * [CHEF-544] - Service provider fails to set @new_resource.updated + * [CHEF-562] - typo in provider/ifconfig.rb + * [CHEF-569] - Remote File causes updates to be sent regardless of idempotency + * [CHEF-578] - Lots of files/subdirectories in a remote_directory cause most chef requests to take 11.5 seconds, and the merb process goes up to 100% CPU + +** Improvement + * [CHEF-503] - cookbooks UI should display the relative path of the template + * [CHEF-546] - Make couchdb version switcher 0.8 specific, and use the new format for everything else. + * [CHEF-559] - distro/ should be under 'chef' dir and packaged w/ gem. + * [CHEF-560] - refactor Chef::Provider::Group::Groupadd + * [CHEF-561] - Flexible application layouts for deploy resource and provider + * [CHEF-566] - Deploy resource/provider callbacks for before_migrate, &etc. should support in-line recipes + * [CHEF-568] - Increase logging for Remote File status, including checksummation. + * [CHEF-580] - faster find_preferred_file + * [CHEF-582] - group resource should allow users or members as a parameter + +** New Feature + * [CHEF-145] - Cron resource: add support for setting cron environment variables like MAILTO or PATH + * [CHEF-419] - Create SCM resource and providers for git & svn + * [CHEF-496] - add simple service provider to chef + +** Task + * [CHEF-571] - LWRP unit tests Release Notes - Chef - Version 0.7.10 @@ -332,12 +332,17 @@ namespace :features do t.profile = "recipe_inclusion" end end + + Cucumber::Rake::Task.new(:lwrp) do |t| + t.profile = "lwrp" + end desc "Run cucumber tests for providers" Cucumber::Rake::Task.new(:provider) do |t| t.profile = "provider" end + namespace :provider do desc "Run cucumber tests for directory resources" Cucumber::Rake::Task.new(:directory) do |t| @@ -364,6 +369,10 @@ namespace :features do t.profile = "provider_template" end + Cucumber::Rake::Task.new(:git) do |t| + t.profile = "provider_git" + end + namespace :package do desc "Run cucumber tests for macports packages" Cucumber::Rake::Task.new(:macports) do |t| diff --git a/chef-server-api/app/controllers/application.rb b/chef-server-api/app/controllers/application.rb index 0a5c335b65..f4fc682a2a 100644 --- a/chef-server-api/app/controllers/application.rb +++ b/chef-server-api/app/controllers/application.rb @@ -169,12 +169,16 @@ class ChefServerApi::Application < Merb::Controller files_list = cookbook.definition_files when :libraries files_list = cookbook.lib_files + when :providers + files_list = cookbook.provider_files + when :resources + files_list = cookbook.resource_files when :files files_list = cookbook.remote_files when :templates files_list = cookbook.template_files else - raise ArgumentError, "segment must be one of :attributes, :recipes, :definitions, :remote_files, :template_files or :libraries" + raise ArgumentError, "segment must be one of :attributes, :recipes, :definitions, :remote_files, :template_files, :resources, :providers or :libraries" end Chef::Log.error(files_list.inspect) files_list @@ -214,9 +218,11 @@ class ChefServerApi::Application < Merb::Controller :libraries => Array.new, :attributes => Array.new, :files => Array.new, - :templates => Array.new + :templates => Array.new, + :resources => Array.new, + :providers => Array.new } - [ :recipes, :definitions, :libraries, :attributes, :files, :templates ].each do |segment| + [ :resources, :providers, :recipes, :definitions, :libraries, :attributes, :files, :templates ].each do |segment| segment_files(segment, cookbook).each do |sf| next if File.directory?(sf) file_name = nil diff --git a/chef-server-slice/app/controllers/cookbook_segment.rb b/chef-server-slice/app/controllers/cookbook_segment.rb new file mode 100644 index 0000000000..61f47f64b0 --- /dev/null +++ b/chef-server-slice/app/controllers/cookbook_segment.rb @@ -0,0 +1,56 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Author:: Christopher Brown (<cb@opscode.com>) +# Author:: Christopher Walters (<cw@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' / 'mixin' / 'checksum' + +class ChefServerSlice::CookbookSegment < ChefServerSlice::Application + + provides :html, :json + + before :login_required + + include Chef::Mixin::Checksum + + def index + if params[:id] + show + else + display load_cookbook_segment(params[:cookbook_id], params[:type]) + end + end + + def show + only_provides :json + files = load_cookbook_segment(params[:cookbook_id], params[:type]) + + raise NotFound, "Cannot find a suitable #{params[:type].to_s.singularize} file!" unless files.has_key?(params[:id]) + to_send = files[params[:id]][:file] + current_checksum = checksum(to_send) + Chef::Log.debug("old sum: #{params[:checksum]}, new sum: #{current_checksum}") + if current_checksum == params[:checksum] + display "File #{to_send} has not changed", :status => 304 + else + send_file(to_send) + end + end + +end + + diff --git a/chef-server-webui/app/views/cookbooks/show.html.haml b/chef-server-webui/app/views/cookbooks/show.html.haml index 94fbff8496..31170a3a73 100644 --- a/chef-server-webui/app/views/cookbooks/show.html.haml +++ b/chef-server-webui/app/views/cookbooks/show.html.haml @@ -1,40 +1,55 @@ .block#block-text .content - %h2.title= "Cookbook: #{h @cookbook["name"]}" + %h2.title= "Cookbook #{h @cookbook.name}" .inner .accordion - - unless @cookbook["libraries"].empty? + - unless @cookbook.lib_files.empty? %h2.head= link_to "Library Files", "#" .files - - @cookbook["libraries"].each do |f| + - @cookbook.lib_files.each do |f| .code - %h4.head= link_to File.basename(f["name"]), "#" - %pre.ruby= syntax_highlight(get_file(f["uri"])) - - unless @cookbook["attributes"].empty? + %h4.head= link_to File.basename(f), "#" + %pre.ruby= syntax_highlight(f) + - unless @cookbook.attribute_files.empty? %h2.head= link_to "Attribute Files", "#" .files - - @cookbook["attributes"].each do |f| + - @cookbook.attribute_files.each do |f| .code - %h4.head= link_to File.basename(f["name"]), "#" - %pre.ruby= syntax_highlight(get_file(f["uri"])) - - unless @cookbook["definitions"].empty? + %h4.head= link_to File.basename(f), "#" + %pre.ruby= syntax_highlight(f) + - unless @cookbook.definition_files.empty? %h2.head= link_to "Definition Files", "#" .files - - @cookbook["definitions"].each do |f| + - @cookbook.definition_files.each do |f| .code - %h4.head= link_to File.basename(f["name"]), "#" - %pre.ruby= syntax_highlight(get_file(f["uri"])) - - unless @cookbook["recipes"].empty? + %h4.head= link_to File.basename(f), "#" + %pre.ruby= syntax_highlight(f) + - unless @cookbook.recipe_files.empty? %h2.head= link_to "Recipe Files", "#" .files - - @cookbook["recipes"].each do |f| + - @cookbook.recipe_files.each do |f| .code - %h4.head= link_to File.basename(f["name"]), "#" - %pre.ruby= syntax_highlight(get_file(f["uri"])) - - unless @cookbook["templates"].empty? + %h4.head= link_to File.basename(f), "#" + %pre.ruby= syntax_highlight(f) + - unless @cookbook.resource_files.empty? + %h2.head= link_to "Resource Files", "#" + .files + - @cookbook.resource_files.each do |f| + .code + %h4.head= link_to File.basename(f), "#" + %pre.ruby= syntax_highlight(f) + - unless @cookbook.provider_files.empty? + %h2.head= link_to "Provider Files", "#" + .files + - @cookbook.provider_files.each do |f| + .code + %h4.head= link_to File.basename(f), "#" + %pre.ruby= syntax_highlight(f) + - unless @cookbook.template_files.empty? %h2.head= link_to "Template Files", "#" .files - - @cookbook["templates"].each do |f| + - @cookbook.template_files.each do |f| + - rel = f.split('templates/',2)[1] .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 rel, "#" + %pre.ruby= syntax_highlight(f) diff --git a/chef-server-webui/public/facebox/closelabel.gif b/chef-server-webui/public/facebox/closelabel.gif Binary files differindex 87b4f8bd69..87b4f8bd69 100755..100644 --- a/chef-server-webui/public/facebox/closelabel.gif +++ b/chef-server-webui/public/facebox/closelabel.gif diff --git a/chef-server-webui/public/facebox/loading.gif b/chef-server-webui/public/facebox/loading.gif Binary files differindex f864d5fd38..f864d5fd38 100755..100644 --- a/chef-server-webui/public/facebox/loading.gif +++ b/chef-server-webui/public/facebox/loading.gif diff --git a/chef-server-webui/public/images/treeBuilderImages/Thumbs.db b/chef-server-webui/public/images/treeBuilderImages/Thumbs.db Binary files differindex 934ed17cb5..934ed17cb5 100755..100644 --- a/chef-server-webui/public/images/treeBuilderImages/Thumbs.db +++ b/chef-server-webui/public/images/treeBuilderImages/Thumbs.db diff --git a/chef-server-webui/public/images/treeBuilderImages/doc.gif b/chef-server-webui/public/images/treeBuilderImages/doc.gif Binary files differindex a0f4dd5dfb..a0f4dd5dfb 100755..100644 --- a/chef-server-webui/public/images/treeBuilderImages/doc.gif +++ b/chef-server-webui/public/images/treeBuilderImages/doc.gif diff --git a/chef-server-webui/public/images/treeBuilderImages/docNode.gif b/chef-server-webui/public/images/treeBuilderImages/docNode.gif Binary files differindex 40167db1a5..40167db1a5 100755..100644 --- a/chef-server-webui/public/images/treeBuilderImages/docNode.gif +++ b/chef-server-webui/public/images/treeBuilderImages/docNode.gif diff --git a/chef-server-webui/public/images/treeBuilderImages/docNodeLast.gif b/chef-server-webui/public/images/treeBuilderImages/docNodeLast.gif Binary files differindex b7b3e55cd0..b7b3e55cd0 100755..100644 --- a/chef-server-webui/public/images/treeBuilderImages/docNodeLast.gif +++ b/chef-server-webui/public/images/treeBuilderImages/docNodeLast.gif diff --git a/chef-server-webui/public/images/treeBuilderImages/docNodeLastFirst.gif b/chef-server-webui/public/images/treeBuilderImages/docNodeLastFirst.gif Binary files differindex 2cbb765d7c..2cbb765d7c 100755..100644 --- a/chef-server-webui/public/images/treeBuilderImages/docNodeLastFirst.gif +++ b/chef-server-webui/public/images/treeBuilderImages/docNodeLastFirst.gif diff --git a/chef-server-webui/public/images/treeBuilderImages/folder.gif b/chef-server-webui/public/images/treeBuilderImages/folder.gif Binary files differindex 3952b3d234..3952b3d234 100755..100644 --- a/chef-server-webui/public/images/treeBuilderImages/folder.gif +++ b/chef-server-webui/public/images/treeBuilderImages/folder.gif diff --git a/chef-server-webui/public/images/treeBuilderImages/folderNode.gif b/chef-server-webui/public/images/treeBuilderImages/folderNode.gif Binary files differindex 5b680136ee..5b680136ee 100755..100644 --- a/chef-server-webui/public/images/treeBuilderImages/folderNode.gif +++ b/chef-server-webui/public/images/treeBuilderImages/folderNode.gif diff --git a/chef-server-webui/public/images/treeBuilderImages/folderNodeFirst.gif b/chef-server-webui/public/images/treeBuilderImages/folderNodeFirst.gif Binary files differindex 9039327677..9039327677 100755..100644 --- a/chef-server-webui/public/images/treeBuilderImages/folderNodeFirst.gif +++ b/chef-server-webui/public/images/treeBuilderImages/folderNodeFirst.gif diff --git a/chef-server-webui/public/images/treeBuilderImages/folderNodeLast.gif b/chef-server-webui/public/images/treeBuilderImages/folderNodeLast.gif Binary files differindex b87f003154..b87f003154 100755..100644 --- a/chef-server-webui/public/images/treeBuilderImages/folderNodeLast.gif +++ b/chef-server-webui/public/images/treeBuilderImages/folderNodeLast.gif diff --git a/chef-server-webui/public/images/treeBuilderImages/folderNodeLastFirst.gif b/chef-server-webui/public/images/treeBuilderImages/folderNodeLastFirst.gif Binary files differindex 64c4d7ae07..64c4d7ae07 100755..100644 --- a/chef-server-webui/public/images/treeBuilderImages/folderNodeLastFirst.gif +++ b/chef-server-webui/public/images/treeBuilderImages/folderNodeLastFirst.gif diff --git a/chef-server-webui/public/images/treeBuilderImages/folderNodeOpen.gif b/chef-server-webui/public/images/treeBuilderImages/folderNodeOpen.gif Binary files differindex b43ce87bf7..b43ce87bf7 100755..100644 --- a/chef-server-webui/public/images/treeBuilderImages/folderNodeOpen.gif +++ b/chef-server-webui/public/images/treeBuilderImages/folderNodeOpen.gif diff --git a/chef-server-webui/public/images/treeBuilderImages/folderNodeOpenFirst.gif b/chef-server-webui/public/images/treeBuilderImages/folderNodeOpenFirst.gif Binary files differindex d8ee1fc1b1..d8ee1fc1b1 100755..100644 --- a/chef-server-webui/public/images/treeBuilderImages/folderNodeOpenFirst.gif +++ b/chef-server-webui/public/images/treeBuilderImages/folderNodeOpenFirst.gif diff --git a/chef-server-webui/public/images/treeBuilderImages/folderNodeOpenLast.gif b/chef-server-webui/public/images/treeBuilderImages/folderNodeOpenLast.gif Binary files differindex 11ae43a5ae..11ae43a5ae 100755..100644 --- a/chef-server-webui/public/images/treeBuilderImages/folderNodeOpenLast.gif +++ b/chef-server-webui/public/images/treeBuilderImages/folderNodeOpenLast.gif diff --git a/chef-server-webui/public/images/treeBuilderImages/folderNodeOpenLastFirst.gif b/chef-server-webui/public/images/treeBuilderImages/folderNodeOpenLastFirst.gif Binary files differindex ba5c0d168d..ba5c0d168d 100755..100644 --- a/chef-server-webui/public/images/treeBuilderImages/folderNodeOpenLastFirst.gif +++ b/chef-server-webui/public/images/treeBuilderImages/folderNodeOpenLastFirst.gif diff --git a/chef-server-webui/public/images/treeBuilderImages/folderOpen.gif b/chef-server-webui/public/images/treeBuilderImages/folderOpen.gif Binary files differindex 7df8d32261..7df8d32261 100755..100644 --- a/chef-server-webui/public/images/treeBuilderImages/folderOpen.gif +++ b/chef-server-webui/public/images/treeBuilderImages/folderOpen.gif diff --git a/chef-server-webui/public/images/treeBuilderImages/vertLine.gif b/chef-server-webui/public/images/treeBuilderImages/vertLine.gif Binary files differindex 63ee93a0f3..63ee93a0f3 100755..100644 --- a/chef-server-webui/public/images/treeBuilderImages/vertLine.gif +++ b/chef-server-webui/public/images/treeBuilderImages/vertLine.gif diff --git a/chef-server-webui/public/javascripts/JSONeditor.js b/chef-server-webui/public/javascripts/JSONeditor.js index b6dac054ba..b6dac054ba 100755..100644 --- a/chef-server-webui/public/javascripts/JSONeditor.js +++ b/chef-server-webui/public/javascripts/JSONeditor.js diff --git a/chef-server-webui/public/javascripts/jquery-1.3.2.min.js b/chef-server-webui/public/javascripts/jquery-1.3.2.min.js index b1ae21d8b2..b1ae21d8b2 100755..100644 --- a/chef-server-webui/public/javascripts/jquery-1.3.2.min.js +++ b/chef-server-webui/public/javascripts/jquery-1.3.2.min.js diff --git a/chef-server-webui/public/javascripts/jquery-ui-1.7.1.custom.min.js b/chef-server-webui/public/javascripts/jquery-ui-1.7.1.custom.min.js index 2ea9f4cd52..2ea9f4cd52 100755..100644 --- a/chef-server-webui/public/javascripts/jquery-ui-1.7.1.custom.min.js +++ b/chef-server-webui/public/javascripts/jquery-ui-1.7.1.custom.min.js diff --git a/chef-server-webui/public/stylesheets/images/ui-bg_diagonals-small_0_aaaaaa_40x40.png b/chef-server-webui/public/stylesheets/images/ui-bg_diagonals-small_0_aaaaaa_40x40.png Binary files differindex d5359734ad..d5359734ad 100755..100644 --- a/chef-server-webui/public/stylesheets/images/ui-bg_diagonals-small_0_aaaaaa_40x40.png +++ b/chef-server-webui/public/stylesheets/images/ui-bg_diagonals-small_0_aaaaaa_40x40.png diff --git a/chef-server-webui/public/stylesheets/images/ui-bg_diagonals-thick_15_444444_40x40.png b/chef-server-webui/public/stylesheets/images/ui-bg_diagonals-thick_15_444444_40x40.png Binary files differindex 88e46a6d36..88e46a6d36 100755..100644 --- a/chef-server-webui/public/stylesheets/images/ui-bg_diagonals-thick_15_444444_40x40.png +++ b/chef-server-webui/public/stylesheets/images/ui-bg_diagonals-thick_15_444444_40x40.png diff --git a/chef-server-webui/public/stylesheets/images/ui-bg_glass_100_f0f0f0_1x400.png b/chef-server-webui/public/stylesheets/images/ui-bg_glass_100_f0f0f0_1x400.png Binary files differindex 33896e710c..33896e710c 100755..100644 --- a/chef-server-webui/public/stylesheets/images/ui-bg_glass_100_f0f0f0_1x400.png +++ b/chef-server-webui/public/stylesheets/images/ui-bg_glass_100_f0f0f0_1x400.png diff --git a/chef-server-webui/public/stylesheets/images/ui-bg_glass_50_99c2ff_1x400.png b/chef-server-webui/public/stylesheets/images/ui-bg_glass_50_99c2ff_1x400.png Binary files differindex 8249158b01..8249158b01 100755..100644 --- a/chef-server-webui/public/stylesheets/images/ui-bg_glass_50_99c2ff_1x400.png +++ b/chef-server-webui/public/stylesheets/images/ui-bg_glass_50_99c2ff_1x400.png diff --git a/chef-server-webui/public/stylesheets/images/ui-bg_glass_55_fbf5d0_1x400.png b/chef-server-webui/public/stylesheets/images/ui-bg_glass_55_fbf5d0_1x400.png Binary files differindex f25dd91065..f25dd91065 100755..100644 --- a/chef-server-webui/public/stylesheets/images/ui-bg_glass_55_fbf5d0_1x400.png +++ b/chef-server-webui/public/stylesheets/images/ui-bg_glass_55_fbf5d0_1x400.png diff --git a/chef-server-webui/public/stylesheets/images/ui-bg_glass_80_e6e6e6_1x400.png b/chef-server-webui/public/stylesheets/images/ui-bg_glass_80_e6e6e6_1x400.png Binary files differindex abaa23f001..abaa23f001 100755..100644 --- a/chef-server-webui/public/stylesheets/images/ui-bg_glass_80_e6e6e6_1x400.png +++ b/chef-server-webui/public/stylesheets/images/ui-bg_glass_80_e6e6e6_1x400.png diff --git a/chef-server-webui/public/stylesheets/images/ui-bg_glass_95_fef1ec_1x400.png b/chef-server-webui/public/stylesheets/images/ui-bg_glass_95_fef1ec_1x400.png Binary files differindex 4443fdc1a1..4443fdc1a1 100755..100644 --- a/chef-server-webui/public/stylesheets/images/ui-bg_glass_95_fef1ec_1x400.png +++ b/chef-server-webui/public/stylesheets/images/ui-bg_glass_95_fef1ec_1x400.png diff --git a/chef-server-webui/public/stylesheets/images/ui-bg_highlight-hard_100_f9f9f9_1x100.png b/chef-server-webui/public/stylesheets/images/ui-bg_highlight-hard_100_f9f9f9_1x100.png Binary files differindex 2cb80364bb..2cb80364bb 100755..100644 --- a/chef-server-webui/public/stylesheets/images/ui-bg_highlight-hard_100_f9f9f9_1x100.png +++ b/chef-server-webui/public/stylesheets/images/ui-bg_highlight-hard_100_f9f9f9_1x100.png diff --git a/chef-server-webui/public/stylesheets/images/ui-bg_highlight-soft_100_e7eef3_1x100.png b/chef-server-webui/public/stylesheets/images/ui-bg_highlight-soft_100_e7eef3_1x100.png Binary files differindex d39c182270..d39c182270 100755..100644 --- a/chef-server-webui/public/stylesheets/images/ui-bg_highlight-soft_100_e7eef3_1x100.png +++ b/chef-server-webui/public/stylesheets/images/ui-bg_highlight-soft_100_e7eef3_1x100.png diff --git a/chef-server-webui/public/stylesheets/images/ui-icons_222222_256x240.png b/chef-server-webui/public/stylesheets/images/ui-icons_222222_256x240.png Binary files differindex 67560da9be..67560da9be 100755..100644 --- a/chef-server-webui/public/stylesheets/images/ui-icons_222222_256x240.png +++ b/chef-server-webui/public/stylesheets/images/ui-icons_222222_256x240.png diff --git a/chef-server-webui/public/stylesheets/images/ui-icons_2694e8_256x240.png b/chef-server-webui/public/stylesheets/images/ui-icons_2694e8_256x240.png Binary files differindex dbd78b68ad..dbd78b68ad 100755..100644 --- a/chef-server-webui/public/stylesheets/images/ui-icons_2694e8_256x240.png +++ b/chef-server-webui/public/stylesheets/images/ui-icons_2694e8_256x240.png diff --git a/chef-server-webui/public/stylesheets/images/ui-icons_2e83ff_256x240.png b/chef-server-webui/public/stylesheets/images/ui-icons_2e83ff_256x240.png Binary files differindex b425c446d2..b425c446d2 100755..100644 --- a/chef-server-webui/public/stylesheets/images/ui-icons_2e83ff_256x240.png +++ b/chef-server-webui/public/stylesheets/images/ui-icons_2e83ff_256x240.png diff --git a/chef-server-webui/public/stylesheets/images/ui-icons_72a7cf_256x240.png b/chef-server-webui/public/stylesheets/images/ui-icons_72a7cf_256x240.png Binary files differindex 58aed4074e..58aed4074e 100755..100644 --- a/chef-server-webui/public/stylesheets/images/ui-icons_72a7cf_256x240.png +++ b/chef-server-webui/public/stylesheets/images/ui-icons_72a7cf_256x240.png diff --git a/chef-server-webui/public/stylesheets/images/ui-icons_888888_256x240.png b/chef-server-webui/public/stylesheets/images/ui-icons_888888_256x240.png Binary files differindex 2e5180e473..2e5180e473 100755..100644 --- a/chef-server-webui/public/stylesheets/images/ui-icons_888888_256x240.png +++ b/chef-server-webui/public/stylesheets/images/ui-icons_888888_256x240.png diff --git a/chef-server-webui/public/stylesheets/images/ui-icons_cd0a0a_256x240.png b/chef-server-webui/public/stylesheets/images/ui-icons_cd0a0a_256x240.png Binary files differindex 2db88b796a..2db88b796a 100755..100644 --- a/chef-server-webui/public/stylesheets/images/ui-icons_cd0a0a_256x240.png +++ b/chef-server-webui/public/stylesheets/images/ui-icons_cd0a0a_256x240.png diff --git a/chef-server-webui/public/stylesheets/images/ui-icons_ffffff_256x240.png b/chef-server-webui/public/stylesheets/images/ui-icons_ffffff_256x240.png Binary files differindex 746e6fa257..746e6fa257 100755..100644 --- a/chef-server-webui/public/stylesheets/images/ui-icons_ffffff_256x240.png +++ b/chef-server-webui/public/stylesheets/images/ui-icons_ffffff_256x240.png diff --git a/chef-server-webui/public/stylesheets/jquery-ui-1.7.1.custom.css b/chef-server-webui/public/stylesheets/jquery-ui-1.7.1.custom.css index 0e93ed5dd1..0e93ed5dd1 100755..100644 --- a/chef-server-webui/public/stylesheets/jquery-ui-1.7.1.custom.css +++ b/chef-server-webui/public/stylesheets/jquery-ui-1.7.1.custom.css diff --git a/chef/Rakefile b/chef/Rakefile index 9735726271..f8d3047076 100644 --- a/chef/Rakefile +++ b/chef/Rakefile @@ -4,7 +4,11 @@ require 'rake/rdoctask' require './tasks/rspec.rb' GEM = "chef" +<<<<<<< HEAD:chef/Rakefile CHEF_VERSION = "0.8.0" +======= +CHEF_VERSION = "0.7.12" +>>>>>>> 6f78b9ff0026b1784375a8baf8076c0cc210c946:chef/Rakefile AUTHOR = "Adam Jacob" EMAIL = "adam@opscode.com" HOMEPAGE = "http://wiki.opscode.com/display/chef" @@ -23,12 +27,19 @@ spec = Gem::Specification.new do |s| s.homepage = HOMEPAGE s.add_dependency "mixlib-config", ">= 1.0.12" +<<<<<<< HEAD:chef/Rakefile %w{mixlib-cli mixlib-log mixlib-authentication json erubis extlib ohai amqp thor ruby-hmac}.each { |gem| s.add_dependency gem } +======= + s.add_dependency "ohai", ">= 0.3.4" + %w{mixlib-cli mixlib-log ruby-openid + json erubis extlib + stomp}.each { |gem| s.add_dependency gem } +>>>>>>> 6f78b9ff0026b1784375a8baf8076c0cc210c946:chef/Rakefile s.bindir = "bin" s.executables = %w( chef-client chef-solo knife ) s.require_path = 'lib' - s.files = %w(LICENSE README.rdoc) + Dir.glob("lib/**/*") + s.files = %w(LICENSE README.rdoc) + Dir.glob("{distro,lib}/**/*") end Rake::GemPackageTask.new(spec) do |pkg| diff --git a/distro/debian/etc/init.d/chef-client b/chef/distro/debian/etc/init.d/chef-client index 02e1d2f114..02e1d2f114 100755 --- a/distro/debian/etc/init.d/chef-client +++ b/chef/distro/debian/etc/init.d/chef-client diff --git a/distro/debian/etc/init.d/chef-indexer b/chef/distro/debian/etc/init.d/chef-indexer index 37126083d5..37126083d5 100755 --- a/distro/debian/etc/init.d/chef-indexer +++ b/chef/distro/debian/etc/init.d/chef-indexer diff --git a/distro/debian/etc/init.d/chef-server b/chef/distro/debian/etc/init.d/chef-server index b116b78414..b116b78414 100755 --- a/distro/debian/etc/init.d/chef-server +++ b/chef/distro/debian/etc/init.d/chef-server diff --git a/distro/debian/man/man1/chef-indexer.1 b/chef/distro/debian/man/man1/chef-indexer.1 index e6dea91883..e6dea91883 100644 --- a/distro/debian/man/man1/chef-indexer.1 +++ b/chef/distro/debian/man/man1/chef-indexer.1 diff --git a/distro/debian/man/man1/chef-server.1 b/chef/distro/debian/man/man1/chef-server.1 index fa5eb71d7e..fa5eb71d7e 100644 --- a/distro/debian/man/man1/chef-server.1 +++ b/chef/distro/debian/man/man1/chef-server.1 diff --git a/distro/debian/man/man8/chef-client.8 b/chef/distro/debian/man/man8/chef-client.8 index b9ebce9a7e..b9ebce9a7e 100644 --- a/distro/debian/man/man8/chef-client.8 +++ b/chef/distro/debian/man/man8/chef-client.8 diff --git a/distro/debian/man/man8/chef-solo.8 b/chef/distro/debian/man/man8/chef-solo.8 index a2e841715c..a2e841715c 100644 --- a/distro/debian/man/man8/chef-solo.8 +++ b/chef/distro/debian/man/man8/chef-solo.8 diff --git a/distro/redhat/etc/chef/client.rb b/chef/distro/redhat/etc/chef/client.rb index 5be5f35114..5be5f35114 100644 --- a/distro/redhat/etc/chef/client.rb +++ b/chef/distro/redhat/etc/chef/client.rb diff --git a/distro/redhat/etc/chef/indexer.rb b/chef/distro/redhat/etc/chef/indexer.rb index 1be9c9f0d2..1be9c9f0d2 100644 --- a/distro/redhat/etc/chef/indexer.rb +++ b/chef/distro/redhat/etc/chef/indexer.rb diff --git a/distro/redhat/etc/chef/server.rb b/chef/distro/redhat/etc/chef/server.rb index 6d2a7794e0..6d2a7794e0 100644 --- a/distro/redhat/etc/chef/server.rb +++ b/chef/distro/redhat/etc/chef/server.rb diff --git a/distro/redhat/etc/init.d/chef-client b/chef/distro/redhat/etc/init.d/chef-client index 332e9606d9..332e9606d9 100644 --- a/distro/redhat/etc/init.d/chef-client +++ b/chef/distro/redhat/etc/init.d/chef-client diff --git a/distro/redhat/etc/init.d/chef-indexer b/chef/distro/redhat/etc/init.d/chef-indexer index a6375de395..a6375de395 100644 --- a/distro/redhat/etc/init.d/chef-indexer +++ b/chef/distro/redhat/etc/init.d/chef-indexer diff --git a/distro/redhat/etc/init.d/chef-server b/chef/distro/redhat/etc/init.d/chef-server index c8ad5c24d1..c8ad5c24d1 100644 --- a/distro/redhat/etc/init.d/chef-server +++ b/chef/distro/redhat/etc/init.d/chef-server diff --git a/chef/lib/chef/client.rb b/chef/lib/chef/client.rb index d42608c76e..ff6fc9d248 100644 --- a/chef/lib/chef/client.rb +++ b/chef/lib/chef/client.rb @@ -1,5 +1,6 @@ # # Author:: Adam Jacob (<adam@opscode.com>) +# Author:: Christopher Walters (<cw@opscode.com>) # Copyright:: Copyright (c) 2008 Opscode, Inc. # License:: Apache License, Version 2.0 # @@ -61,6 +62,10 @@ class Chef # * build_node - Get the last known state, merge with local changes # * register - Make sure we have an openid # * authenticate - Authenticate with our openid + # * sync_library_files - Populate the local cache with all the library files + # * sync_provider_files - Populate the local cache with all the provider files + # * sync_resource_files - Populate the local cache with all the resource files + # * sync_attribute_files - Populate the local cache with all the attribute files # * sync_definitions - Populate the local cache with all the definitions # * sync_recipes - Populate the local cache with all the recipes # * do_attribute_files - Populate the local cache with all attributes, and execute them @@ -98,7 +103,7 @@ class Chef def run_solo start_time = Time.now Chef::Log.info("Starting Chef Solo Run") - + determine_node_name build_node(@node_name, true) converge(true) @@ -118,7 +123,7 @@ class Chef def determine_node_name run_ohai - unless @safe_name && @node_name + unless safe_name && node_name if Chef::Config[:node_name] @node_name = Chef::Config[:node_name] else @@ -211,7 +216,7 @@ class Chef file_canonical = Hash.new - [ "recipes", "attributes", "definitions", "libraries" ].each do |segment| + [ "recipes", "attributes", "definitions", "libraries", "resources", "providers" ].each do |segment| remote_list = parts[segment] # segement = cookbook segment @@ -312,13 +317,9 @@ class Chef Chef::Config[:cookbook_path] = File.join(Chef::Config[:file_cache_path], "cookbooks") end compile = Chef::Compile.new(@node) - compile.load_libraries - compile.load_attributes - compile.load_definitions - compile.load_recipes - + Chef::Log.debug("Converging node #{@safe_name}") - cr = Chef::Runner.new(@node, compile.collection) + cr = Chef::Runner.new(@node, compile.collection, compile.definitions, compile.cookbook_loader) cr.converge true end diff --git a/chef/lib/chef/compile.rb b/chef/lib/chef/compile.rb index 74c281131d..b6eaf77e8f 100644 --- a/chef/lib/chef/compile.rb +++ b/chef/lib/chef/compile.rb @@ -1,5 +1,6 @@ # # Author:: Adam Jacob (<adam@opscode.com>) +# Author:: Christopher Walters (<cw@opscode.com>) # Copyright:: Copyright (c) 2008 Opscode, Inc. # License:: Apache License, Version 2.0 # @@ -27,8 +28,8 @@ class Chef attr_accessor :node, :cookbook_loader, :collection, :definitions - # Creates a new Chef::Compile object. This object gets used by the Chef Server to generate - # a fully compiled recipe list for a node. + # Creates a new Chef::Compile object and populates its fields. This object gets + # used by the Chef Server to generate a fully compiled recipe list for a node. # # === Returns # object<Chef::Compile>:: Duh. :) @@ -40,6 +41,13 @@ class Chef @recipes = Array.new @default_attributes = Array.new @override_attributes = Array.new + + load_libraries + load_providers + load_resources + load_attributes + load_definitions + load_recipes end # Looks up the node via the "name" argument, first from CouchDB, then by calling @@ -97,6 +105,30 @@ class Chef end true end + + # Load all the providers, from every cookbook, so they are available when we process + # the recipes. + # + # === Returns + # true:: Always returns true + def load_providers() + @cookbook_loader.each do |cookbook| + cookbook.load_providers + end + true + end + + # Load all the resources, from every cookbook, so they are available when we process + # the recipes. + # + # === Returns + # true:: Always returns true + def load_resources() + @cookbook_loader.each do |cookbook| + cookbook.load_resources + end + true + end # Load all the recipes specified in the node data (loaded via load_node, above.) # diff --git a/chef/lib/chef/cookbook.rb b/chef/lib/chef/cookbook.rb index f6f2899ef4..0d92d02c4f 100644 --- a/chef/lib/chef/cookbook.rb +++ b/chef/lib/chef/cookbook.rb @@ -1,6 +1,7 @@ # # Author:: Adam Jacob (<adam@opscode.com>) # Author:: Nuo Yan (<nuo@opscode.com>) +# Author:: Christopher Walters (<cw@opscode.com>) # Copyright:: Copyright (c) 2008 Opscode, Inc. # License:: Apache License, Version 2.0 # @@ -20,12 +21,14 @@ require 'chef/log' require 'chef/node' require 'chef/resource_definition' require 'chef/recipe' +require 'chef/mixin/convert_to_class_name' class Chef class Cookbook + include Chef::Mixin::ConvertToClassName attr_accessor :attribute_files, :definition_files, :template_files, :remote_files, - :lib_files, :name + :lib_files, :resource_files, :provider_files, :name attr_reader :recipe_files # Creates a new Chef::Cookbook object. @@ -41,6 +44,8 @@ class Chef @recipe_files = Array.new @recipe_names = Hash.new @lib_files = Array.new + @resource_files = Array.new + @provider_files = Array.new end # Loads all the library files in this cookbook via require. @@ -90,6 +95,28 @@ class Chef end results end + + # Loads all the resources in this cookbook. + # + # === Returns + # true:: Always returns true + def load_resources + @resource_files.each do |file| + Chef::Log.debug("Loading cookbook #{name}'s resources from #{file}") + Chef::Resource.build_from_file(name, file) + end + end + + # Loads all the providers in this cookbook. + # + # === Returns + # true:: Always returns true + def load_providers + @provider_files.each do |file| + Chef::Log.debug("Loading cookbook #{name}'s providers from #{file}") + Chef::Provider.build_from_file(name, file) + end + end def recipe_files=(*args) @recipe_files = args.flatten @@ -142,4 +169,4 @@ class Chef end end -end
\ No newline at end of file +end diff --git a/chef/lib/chef/cookbook_loader.rb b/chef/lib/chef/cookbook_loader.rb index 5d57a3a308..56434f66e1 100644 --- a/chef/lib/chef/cookbook_loader.rb +++ b/chef/lib/chef/cookbook_loader.rb @@ -1,6 +1,9 @@ # # Author:: Adam Jacob (<adam@opscode.com>) +# Author:: Christopher Walters (<cw@opscode.com>) +# Author:: Daniel DeLeo (<dan@kallistec.com>) # Copyright:: Copyright (c) 2008 Opscode, Inc. +# Copyright:: Copyright (c) 2009 Daniel DeLeo # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -29,74 +32,86 @@ class Chef def initialize() @cookbook = Hash.new @metadata = Hash.new + @ignore_regexes = Hash.new { |hsh, key| hsh[key] = Array.new } load_cookbooks end def load_cookbooks cookbook_settings = Hash.new - Chef::Config.cookbook_path.each do |cb_path| + [Chef::Config.cookbook_path].flatten.reverse.each do |cb_path| Dir[File.join(cb_path, "*")].each do |cookbook| next unless File.directory?(cookbook) cookbook_name = File.basename(cookbook).to_sym unless cookbook_settings.has_key?(cookbook_name) cookbook_settings[cookbook_name] = { - :ignore_regexes => Array.new, - :attribute_files => Array.new, - :definition_files => Array.new, - :recipe_files => Array.new, - :template_files => Array.new, - :remote_files => Array.new, - :lib_files => Array.new, - :metadata_files => Array.new + :attribute_files => Hash.new, + :definition_files => Hash.new, + :recipe_files => Hash.new, + :template_files => Hash.new, + :remote_files => Hash.new, + :lib_files => Hash.new, + :resource_files => Hash.new, + :provider_files => Hash.new, + :metadata_files => Array.new } end ignore_regexes = load_ignore_file(File.join(cookbook, "ignore")) - cookbook_settings[cookbook_name][:ignore_regexes].concat(ignore_regexes) + @ignore_regexes[cookbook_name].concat(ignore_regexes) + load_files_unless_basename( File.join(cookbook, "attributes", "*.rb"), - cookbook_settings[cookbook_name][:attribute_files], - cookbook_settings[cookbook_name][:ignore_regexes] + cookbook_settings[cookbook_name][:attribute_files] ) load_files_unless_basename( File.join(cookbook, "definitions", "*.rb"), - cookbook_settings[cookbook_name][:definition_files], - cookbook_settings[cookbook_name][:ignore_regexes] + cookbook_settings[cookbook_name][:definition_files] ) load_files_unless_basename( File.join(cookbook, "recipes", "*.rb"), - cookbook_settings[cookbook_name][:recipe_files], - cookbook_settings[cookbook_name][:ignore_regexes] + cookbook_settings[cookbook_name][:recipe_files] ) load_files_unless_basename( File.join(cookbook, "libraries", "*.rb"), - cookbook_settings[cookbook_name][:lib_files], - cookbook_settings[cookbook_name][:ignore_regexes] + cookbook_settings[cookbook_name][:lib_files] ) load_cascading_files( "*.erb", File.join(cookbook, "templates"), - cookbook_settings[cookbook_name][:template_files], - cookbook_settings[cookbook_name][:ignore_regexes] + cookbook_settings[cookbook_name][:template_files] ) load_cascading_files( "*", File.join(cookbook, "files"), - cookbook_settings[cookbook_name][:remote_files], - cookbook_settings[cookbook_name][:ignore_regexes] + cookbook_settings[cookbook_name][:remote_files] ) + load_cascading_files( + "*.rb", + File.join(cookbook, "resources"), + cookbook_settings[cookbook_name][:resource_files] + ) + load_cascading_files( + "*.rb", + File.join(cookbook, "providers"), + cookbook_settings[cookbook_name][:provider_files] + ) + if File.exists?(File.join(cookbook, "metadata.json")) cookbook_settings[cookbook_name][:metadata_files] << File.join(cookbook, "metadata.json") end end end + remove_ignored_files_from(cookbook_settings) + cookbook_settings.each_key do |cookbook| @cookbook[cookbook] = Chef::Cookbook.new(cookbook) - @cookbook[cookbook].attribute_files = cookbook_settings[cookbook][:attribute_files] - @cookbook[cookbook].definition_files = cookbook_settings[cookbook][:definition_files] - @cookbook[cookbook].recipe_files = cookbook_settings[cookbook][:recipe_files] - @cookbook[cookbook].template_files = cookbook_settings[cookbook][:template_files] - @cookbook[cookbook].remote_files = cookbook_settings[cookbook][:remote_files] - @cookbook[cookbook].lib_files = cookbook_settings[cookbook][:lib_files] + @cookbook[cookbook].attribute_files = cookbook_settings[cookbook][:attribute_files].values + @cookbook[cookbook].definition_files = cookbook_settings[cookbook][:definition_files].values + @cookbook[cookbook].recipe_files = cookbook_settings[cookbook][:recipe_files].values + @cookbook[cookbook].template_files = cookbook_settings[cookbook][:template_files].values + @cookbook[cookbook].remote_files = cookbook_settings[cookbook][:remote_files].values + @cookbook[cookbook].lib_files = cookbook_settings[cookbook][:lib_files].values + @cookbook[cookbook].resource_files = cookbook_settings[cookbook][:resource_files].values + @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)) @@ -133,35 +148,32 @@ class Chef results end - def load_cascading_files(file_glob, base_path, result_array, ignore_regexes) - # To handle dotfiles like .ssh - Dir.glob(File.join(base_path, "**/#{file_glob}"), File::FNM_DOTMATCH).each do |file| - next if skip_file(file, ignore_regexes) - file =~ /^#{base_path}\/(.+)$/ - singlecopy = $1 - unless result_array.detect { |f| f =~ /#{singlecopy}$/ } - result_array << file + def remove_ignored_files_from(cookbook_settings) + file_types_to_inspect = [ :attribute_files, :definition_files, :recipe_files, :template_files, + :remote_files, :lib_files, :resource_files, :provider_files] + + @ignore_regexes.each do |cookbook_name, regexes| + regexes.each do |regex| + settings = cookbook_settings[cookbook_name] + file_types_to_inspect.each do |file_type| + settings[file_type].delete_if { |uniqname, fullpath| fullpath.match(regex) } + end end end end - def load_files_unless_basename(file_glob, result_array, ignore_regexes) - Dir[file_glob].each do |file| - next if skip_file(file, ignore_regexes) - file_basename = File.basename(file) - # If we've seen a file with this basename before, skip it. - unless result_array.detect { |f| File.basename(f) == file_basename } - result_array << file - end + def load_cascading_files(file_glob, base_path, result_hash) + rm_base_path = /^#{base_path}\/(.+)$/ + # To handle dotfiles like .ssh + Dir.glob(File.join(base_path, "**/#{file_glob}"), File::FNM_DOTMATCH).each do |file| + result_hash[rm_base_path.match(file)[1]] = file end end - def skip_file(file, ignore_regexes) - skip = false - ignore_regexes.each do |exp| - skip = true if exp.match(file) + def load_files_unless_basename(file_glob, result_hash) + Dir[file_glob].each do |file| + result_hash[File.basename(file)] = file end - skip end end diff --git a/chef/lib/chef/mixin/command.rb b/chef/lib/chef/mixin/command.rb index 0046341f14..946c16da71 100644 --- a/chef/lib/chef/mixin/command.rb +++ b/chef/lib/chef/mixin/command.rb @@ -112,13 +112,22 @@ class Chef end end + status, stdout, stderr = output_of_command(args[:command], args) + command_output << "STDOUT: #{stdout}" + command_output << "STDERR: #{stderr}" + handle_command_failures(status, command_output, args) + + status + end + + module_function :run_command + + def output_of_command(command, args) + Chef::Log.debug("Executing #{command}") + stderr_string, stdout_string, status = "", "", nil + exec_processing_block = lambda do |pid, stdin, stdout, stderr| - Chef::Log.debug("---- Begin output of #{args[:command]} ----") - Chef::Log.debug("STDOUT: #{stdout.string.chomp}") - Chef::Log.debug("STDERR: #{stderr.string.chomp}") - command_output << "STDOUT: #{stdout.string.chomp}" - command_output << "STDERR: #{stderr.string.chomp}" - Chef::Log.debug("---- End output of #{args[:command]} ----") + stdout_string, stderr_string = stdout.string.chomp, stderr.string.chomp end args[:cwd] ||= Dir.tmpdir @@ -126,44 +135,65 @@ class Chef raise Chef::Exceptions::Exec, "#{args[:cwd]} does not exist or is not a directory" end - Chef::Log.debug("Executing #{args[:command]}") - - status = nil - Dir.chdir(args[:cwd]) do if args[:timeout] begin Timeout.timeout(args[:timeout]) do - status = popen4(args[:command], args, &exec_processing_block) + status = popen4(command, args, &exec_processing_block) end rescue Timeout::Error => e - Chef::Log.error("#{args[:command]} exceeded timeout #{args[:timeout]}") + Chef::Log.error("#{command} exceeded timeout #{args[:timeout]}") raise(e) end else - status = popen4(args[:command], args, &exec_processing_block) + status = popen4(command, args, &exec_processing_block) end - unless args[:ignore_failure] - args[:returns] ||= 0 - if status.exitstatus != args[:returns] - # if the log level is not debug, through output of command when we fail - output = "" - if Chef::Log.logger.level > 0 - output << "\n---- Begin output of #{args[:command]} ----\n" - output << "#{command_output}" - output << "---- End output of #{args[:command]} ----\n" - end - raise Chef::Exceptions::Exec, "#{args[:command]} returned #{status.exitstatus}, expected #{args[:returns]}#{output}" + Chef::Log.debug("---- Begin output of #{command} ----") + Chef::Log.debug("STDOUT: #{stdout_string}") + Chef::Log.debug("STDERR: #{stderr_string}") + Chef::Log.debug("---- End output of #{command} ----") + Chef::Log.debug("Ran #{command} returned #{status.exitstatus}") + end + + return status, stdout_string, stderr_string + end + + 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] + # if the log level is not debug, through output of command when we fail + output = "" + if Chef::Log.logger.level > 0 + output << "\n---- Begin output of #{args[:command]} ----\n" + output << "#{command_output}" + output << "---- End output of #{args[:command]} ----\n" end + raise Chef::Exceptions::Exec, "#{args[:command]} returned #{status.exitstatus}, expected #{args[:returns]}#{output}" end - Chef::Log.debug("Ran #{args[:command]} returned #{status.exitstatus}") end - status end - module_function :run_command + module_function :handle_command_failures + # Call #run_command but set LC_ALL to the system's current environment so it doesn't get changed to C. + # + # === Parameters + # args<Hash>: A number of required and optional arguments that will be handed out to #run_command + # + # === Returns + # Returns the result of #run_command + def run_command_with_systems_locale(args={}) + args[:environment] ||= {} + args[:environment]["LC_ALL"] = ENV["LC_ALL"] + run_command args + end + + module_function :run_command_with_systems_locale + # This is taken directly from Ara T Howard's Open4 library, and then # modified to suit the needs of Chef. Any bugs here are most likely # my own, and not Ara's. @@ -189,7 +219,14 @@ class Chef unless args[:group].kind_of?(Integer) args[:group] = Etc.getgrnam(args[:group]).gid if args[:group] end - args[:environment] ||= nil + args[:environment] ||= {} + + # Default on C locale so parsing commands output can be done + # independently of the node's default locale. + # "LC_ALL" could be set to nil, in which case we also must ignore it. + unless args[:environment].has_key?("LC_ALL") + args[:environment]["LC_ALL"] = "C" + end pw, pr, pe, ps = IO.pipe, IO.pipe, IO.pipe, IO.pipe @@ -223,10 +260,8 @@ class Chef Process.uid = args[:user] end - if args[:environment] - args[:environment].each do |key,value| - ENV[key] = value - end + args[:environment].each do |key,value| + ENV[key] = value end if args[:umask] diff --git a/chef/lib/chef/mixin/convert_to_class_name.rb b/chef/lib/chef/mixin/convert_to_class_name.rb new file mode 100644 index 0000000000..d320eb8311 --- /dev/null +++ b/chef/lib/chef/mixin/convert_to_class_name.rb @@ -0,0 +1,48 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Author:: Christopher Walters (<cw@opscode.com>) +# Copyright:: Copyright (c) 2008, 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. +# + +class Chef + module Mixin + module ConvertToClassName + + def convert_to_class_name(str) + rname = nil + regexp = %r{^(.+?)(_(.+))?$} + + mn = str.match(regexp) + if mn + rname = mn[1].capitalize + + while mn && mn[3] + mn = mn[3].match(regexp) + rname << mn[1].capitalize if mn + end + end + + rname + end + + def filename_to_qualified_string(base, filename) + file_base = File.basename(filename, ".rb") + base.to_s + (file_base == 'default' ? '' : "_#{file_base}") + end + + end + end +end diff --git a/chef/lib/chef/mixin/find_preferred_file.rb b/chef/lib/chef/mixin/find_preferred_file.rb index 2c3fd771de..de254bb801 100644 --- a/chef/lib/chef/mixin/find_preferred_file.rb +++ b/chef/lib/chef/mixin/find_preferred_file.rb @@ -72,26 +72,17 @@ class Chef File.join("#{platform}", "#{file_name}"), File.join("default", "#{file_name}") ] - to_send = nil + file_list_str = file_list.keys.join("\n") preferences.each do |pref| Chef::Log.debug("Looking for #{pref}") - file_list.each_key do |file| - Chef::Log.debug("Checking for #{pref} #{file} ") - if file =~ /#{pref}$/ - Chef::Log.debug("Matched #{pref} for #{file}!") - to_send = file - break - end + matcher = /^(.+#{pref})$/ + if match = matcher.match(file_list_str) + return match[1] end - break if to_send end - unless to_send - raise Chef::Exceptions::FileNotFound, "Cannot find a preferred file for #{file_name}!" - end - - to_send + raise Chef::Exceptions::FileNotFound, "Cannot find a preferred file for #{file_name}!" end end diff --git a/chef/lib/chef/mixin/from_file.rb b/chef/lib/chef/mixin/from_file.rb index 482ee0bf05..609fe1de55 100644 --- a/chef/lib/chef/mixin/from_file.rb +++ b/chef/lib/chef/mixin/from_file.rb @@ -1,5 +1,6 @@ # # Author:: Adam Jacob (<adam@opscode.com>) +# Author:: Christopher Walters (<cw@opscode.com>) # Copyright:: Copyright (c) 2008 Opscode, Inc. # License:: Apache License, Version 2.0 # @@ -31,6 +32,19 @@ class Chef raise IOError, "Cannot open or read #{filename}!" end end + + # Loads a given ruby file, and runs class_eval against it in the context of the current + # object. + # + # Raises an IOError if the file cannot be found, or is not readable. + def class_from_file(filename) + if File.exists?(filename) && File.readable?(filename) + self.class_eval(IO.read(filename), filename, 1) + else + raise IOError, "Cannot open or read #{filename}!" + end + end + end end end diff --git a/chef/lib/chef/mixin/generate_url.rb b/chef/lib/chef/mixin/generate_url.rb index 6a3bfbb73e..c5b82c6d3f 100644 --- a/chef/lib/chef/mixin/generate_url.rb +++ b/chef/lib/chef/mixin/generate_url.rb @@ -23,6 +23,7 @@ class Chef module GenerateURL def generate_cookbook_url(url, cookbook, type, node, args=nil) + Chef::Log.debug("generating cookbook url for url=#{url}, cookbook=#{cookbook.inspect}, type=#{type}, node=#{node}") new_url = nil if url =~ /^(http|https):\/\// new_url = url @@ -31,7 +32,7 @@ class Chef new_url += "id=#{url}" new_url = generate_cookbook_url_from_uri(new_url, node, args) end - + Chef::Log.debug("generated cookbook url: #{new_url}") return new_url end diff --git a/chef/lib/chef/mixin/recipe_definition_dsl_core.rb b/chef/lib/chef/mixin/recipe_definition_dsl_core.rb new file mode 100644 index 0000000000..65d0cc9cfc --- /dev/null +++ b/chef/lib/chef/mixin/recipe_definition_dsl_core.rb @@ -0,0 +1,77 @@ +# +# Author:: Adam Jacob (<adam@opscode.com>) +# Author:: Christopher Walters (<cw@opscode.com>) +# Copyright:: Copyright (c) 2008, 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/recipe' +require 'chef/resource' +require 'chef/mixin/convert_to_class_name' + +class Chef + module Mixin + module RecipeDefinitionDSLCore + + include Chef::Mixin::ConvertToClassName + + def method_missing(method_symbol, *args, &block) + # If we have a definition that matches, we want to use that instead. This should + # let you do some really crazy over-riding of "native" types, if you really want + # to. + if @definitions.has_key?(method_symbol) + # This dupes the high level object, but we still need to dup the params + new_def = @definitions[method_symbol].dup + new_def.params = new_def.params.dup + new_def.node = @node + # This sets up the parameter overrides + new_def.instance_eval(&block) if block + new_recipe = Chef::Recipe.new(@cookbook_name, @recipe_name, @node, @collection, @definitions, @cookbook_loader) + new_recipe.params = new_def.params + new_recipe.params[:name] = args[0] + new_recipe.instance_eval(&new_def.recipe) + else + # Otherwise, we're rocking the regular resource call route. + method_name = method_symbol.to_s + rname = convert_to_class_name(method_name) + + # If we have a resource like this one, we want to steal its state + resource = begin + args << @collection + args << @node + Chef::Resource.const_get(rname).new(*args) + rescue NameError => e + if e.to_s =~ /Chef::Resource/ + raise NameError, "Cannot find #{rname} for #{method_name}\nOriginal exception: #{e.class}: #{e.message}" + else + raise e + end + end + resource.load_prior_resource + resource.cookbook_name = @cookbook_name + resource.recipe_name = @recipe_name + resource.params = @params + # Determine whether this resource is being created in the context of an enclosing Provider + resource.enclosing_provider = self.is_a?(Chef::Provider) ? self : nil + resource.instance_eval(&block) if block + + @collection.insert(resource) + resource + end + end + + end + end +end diff --git a/chef/lib/chef/platform.rb b/chef/lib/chef/platform.rb index c59b25811b..cbb8e55a92 100644 --- a/chef/lib/chef/platform.rb +++ b/chef/lib/chef/platform.rb @@ -93,7 +93,7 @@ class Chef :bash => Chef::Provider::Script, :csh => Chef::Provider::Script, :user => Chef::Provider::User::Useradd, - :group => Chef::Provider::Group::Groupadd, + :group => Chef::Provider::Group::Gpasswd, :http_request => Chef::Provider::HttpRequest, :route => Chef::Provider::Route, :ifconfig => Chef::Provider::Ifconfig, diff --git a/chef/lib/chef/provider.rb b/chef/lib/chef/provider.rb index 25fbf11bc5..2191aabc94 100644 --- a/chef/lib/chef/provider.rb +++ b/chef/lib/chef/provider.rb @@ -1,6 +1,7 @@ # # Author:: Adam Jacob (<adam@opscode.com>) -# Copyright:: Copyright (c) 2008 Opscode, Inc. +# Author:: Christopher Walters (<cw@opscode.com>) +# Copyright:: Copyright (c) 2008, 2009 Opscode, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,15 +17,25 @@ # limitations under the License. # +require 'chef/mixin/from_file' +require 'chef/mixin/convert_to_class_name' +require 'chef/mixin/recipe_definition_dsl_core' + class Chef class Provider + include Chef::Mixin::RecipeDefinitionDSLCore + attr_accessor :node, :new_resource, :current_resource - def initialize(node, new_resource) + def initialize(node, new_resource, collection=nil, definitions={}, cookbook_loader=nil) @node = node @new_resource = new_resource @current_resource = nil + @collection = collection + @definitions = definitions + @cookbook_loader = cookbook_loader + @cookbook_name = @new_resource.cookbook_name end def load_current_resource @@ -36,5 +47,55 @@ class Chef true end + protected + + def recipe_eval(*args, &block) + provider_collection, @collection = @collection, Chef::ResourceCollection.new + + instance_eval(*args, &block) + Chef::Runner.new(@node, @collection).converge + + @collection = provider_collection + end + + public + + class << self + include Chef::Mixin::ConvertToClassName + + def build_from_file(cookbook_name, filename) + pname = filename_to_qualified_string(cookbook_name, filename) + + new_provider_class = Class.new self do |cls| + + def load_current_resource + # silence Chef::Exceptions::Override exception + end + + class << cls + include Chef::Mixin::FromFile + + # setup DSL's shortcut methods + def action(name, &block) + define_method("action_#{name.to_s}") do + instance_eval(&block) + end + end + end + + # load provider definition from file + cls.class_from_file(filename) + end + + # register new class as a Chef::Provider + pname = filename_to_qualified_string(cookbook_name, filename) + class_name = convert_to_class_name(pname) + Chef::Provider.const_set(class_name, new_provider_class) + Chef::Log.debug("Loaded contents of #{filename} into a provider named #{pname} defined in Chef::Provider::#{class_name}") + + new_provider_class + end + end + end end diff --git a/chef/lib/chef/provider/cron.rb b/chef/lib/chef/provider/cron.rb index 9e633053ce..cd8b59c97e 100644 --- a/chef/lib/chef/provider/cron.rb +++ b/chef/lib/chef/provider/cron.rb @@ -25,8 +25,8 @@ class Chef class Cron < Chef::Provider include Chef::Mixin::Command - def initialize(node, new_resource) - super(node, new_resource) + def initialize(node, new_resource, collection=nil, definitions=nil, cookbook_loader=nil) + super(node, new_resource, collection, definitions, cookbook_loader) @cron_exists = false @cron_empty = false end @@ -35,17 +35,46 @@ class Chef def load_current_resource crontab = String.new @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 } 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}/ Chef::Log.debug("Found cron '#{@new_resource.name}'") + cron_found = true @cron_exists = true + next + when /^MAILTO=(\S*)/ + @current_resource.mailto($1) if cron_found + next + when /^PATH=(\S*)/ + @current_resource.path($1) if cron_found + next + when /^SHELL=(\S*)/ + @current_resource.shell($1) if cron_found + next + when /^HOME=(\S*)/ + @current_resource.home($1) if cron_found + next + when /([0-9\*]+)\s*([0-9\*]+)\s*([0-9\*]+)\s*([0-9\*]+)\s*([0-9\*]+)\s*(.*)/ + if cron_found + @current_resource.minute($1) + @current_resource.hour($2) + @current_resource.day($3) + @current_resource.month($4) + @current_resource.weekday($5) + @current_resource.command($6) + cron_found=false + end + next + else + next end end Chef::Log.debug("Cron '#{@new_resource.name}' not found") unless @cron_exists @@ -57,36 +86,53 @@ class Chef @current_resource end + def compare_cron + [ :minute, :hour, :day, :month, :weekday, :command, :mailto, :path, :shell, :home ].any? do |cron_var| + !@new_resource.send(cron_var).nil? && @new_resource.send(cron_var) != @current_resource.send(cron_var) + end + end + def action_create crontab = String.new + newcron = String.new cron_found = false + + newcron << "# Chef Name: #{new_resource.name}\n" + [ :mailto, :path, :shell, :home ].each do |v| + newcron << "#{v.to_s.upcase}=#{@new_resource.send(v)}\n" if @new_resource.send(v) + end + newcron << "#{@new_resource.minute} #{@new_resource.hour} #{@new_resource.day} #{@new_resource.month} #{@new_resource.weekday} #{@new_resource.command}\n" + if @cron_exists + unless compare_cron + Chef::Log.debug("Skipping existing cron entry '#{@new_resource.name}'") + return + end status = popen4("crontab -l -u #{@new_resource.user}") do |pid, stdin, stdout, stderr| stdout.each_line do |line| - if cron_found - cronline = "#{@new_resource.minute} #{@new_resource.hour} #{@new_resource.day} #{@new_resource.month} #{@new_resource.weekday} #{@new_resource.command}\n" - if (line == cronline) - Chef::Log.debug("Skipping existing cron entry '#{@new_resource.name}'") - return - end - crontab << cronline - cron_found = false - next - end case line - when /^# Chef Name: #{new_resource.name}\n/ + when /^# Chef Name: #{@new_resource.name}\n/ cron_found = true + next + when /([0-9\*]+)\s*([0-9\*]+)\s*([0-9\*]+)\s*([0-9\*]+)\s*([0-9\*]+)\s*(.*)/ + if cron_found + cron_found = false + crontab << newcron + next + end + else + next if cron_found end crontab << line end end - status = popen4("crontab -u #{@new_resource.user} -", :waitlast => true) do |pid, stdin, stdout, stderr| crontab.each { |line| stdin.puts "#{line}" } stdin.close end Chef::Log.info("Updated cron '#{@new_resource.name}'") + @new_resource.updated = true else unless @cron_empty status = popen4("crontab -l -u #{@new_resource.user}") do |pid, stdin, stdout, stderr| @@ -94,14 +140,14 @@ class Chef end end - crontab << "# Chef Name: #{new_resource.name}\n" - crontab << "#{@new_resource.minute} #{@new_resource.hour} #{@new_resource.day} #{@new_resource.month} #{@new_resource.weekday} #{@new_resource.command}\n" - + crontab << newcron + status = popen4("crontab -u #{@new_resource.user} -", :waitlast => true) do |pid, stdin, stdout, stderr| crontab.each { |line| stdin.puts "#{line}" } stdin.close end Chef::Log.info("Added cron '#{@new_resource.name}'") + @new_resource.updated = true end end @@ -111,14 +157,17 @@ class Chef cron_found = false status = popen4("crontab -l -u #{@new_resource.user}") do |pid, stdin, stdout, stderr| stdout.each_line do |line| - if cron_found - cron_found = false - next - end case line - when /^# Chef Name: #{new_resource.name}\n/ + when /^# Chef Name: #{@new_resource.name}\n/ cron_found = true next + when /([0-9\*]+)\s*([0-9\*]+)\s*([0-9\*]+)\s*([0-9\*]+)\s*([0-9\*]+)\s*(.*)/ + if cron_found + cron_found = false + next + end + else + next if cron_found end crontab << line end @@ -129,6 +178,7 @@ class Chef stdin.close end Chef::Log.debug("Deleted cron '#{@new_resource.name}'") + @new_resource.updated = true end end diff --git a/chef/lib/chef/provider/deploy.rb b/chef/lib/chef/provider/deploy.rb new file mode 100644 index 0000000000..60e8766002 --- /dev/null +++ b/chef/lib/chef/provider/deploy.rb @@ -0,0 +1,281 @@ +# +# Author:: Daniel DeLeo (<dan@kallistec.com>) +# Copyright:: Copyright (c) 2008 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require "chef/mixin/command" +require "chef/mixin/from_file" +require "chef/provider/git" +require "chef/provider/subversion" + +class Chef + class Provider + class Deploy < Chef::Provider + + include Chef::Mixin::FromFile + include Chef::Mixin::Command + + attr_reader :scm_provider, :release_path + + def initialize(node, new_resource, collection=nil, definitions=nil, cookbook_loader=nil) + super(node, new_resource, collection, definitions, cookbook_loader) + + # NOTE: workaround for CHEF-577 + @definitions ||= Hash.new + @collection = Chef::ResourceCollection.new + + @scm_provider = @new_resource.scm_provider.new(@node, @new_resource) + + # @configuration is not used by Deploy, it is only for backwards compat with + # chef-deploy or capistrano hooks that might use it to get environment information + @configuration = @new_resource.to_hash + @configuration[:environment] = @configuration[:environment] && @configuration[:environment]["RAILS_ENV"] + end + + def load_current_resource + @release_path = @new_resource.deploy_to + "/releases/#{release_slug}" + end + + def sudo(command,&block) + execute(command, &block) + end + + def run(command, &block) + exec = execute(command, &block) + exec.user(@new_resource.user) + exec + end + + def action_deploy + if all_releases.include?(release_path) + Chef::Log.info("Already deployed app at #{release_path}, skipping. Use action :force_deploy to force.") + else + deploy + end + end + + def action_force_deploy + if all_releases.include?(release_path) + Chef::Log.info("Already deployed app at #{release_path}, forcing.") + FileUtils.rm_rf(release_path) + end + deploy + end + + def action_rollback + @release_path = all_releases[-2] + raise RuntimeError, "There is no release to rollback to!" unless @release_path + release_to_nuke = all_releases.last + Chef::Log.info "rolling back to previous release: #{release_path}" + symlink + Chef::Log.info "removing last release: #{release_to_nuke}" + FileUtils.rm_rf release_to_nuke + Chef::Log.info "restarting with previous release" + restart + end + + def deploy + Chef::Log.info "deploying branch: #{@new_resource.branch}" + enforce_ownership + update_cached_repo + copy_cached_repo + install_gems + enforce_ownership + callback(:before_migrate, @new_resource.before_migrate) + migrate + callback(:before_symlink, @new_resource.before_symlink) + symlink + callback(:before_restart, @new_resource.before_restart) + restart + callback(:after_restart, @new_resource.after_restart) + cleanup! + end + + def callback(what, callback_code=nil) + @collection = Chef::ResourceCollection.new + case callback_code + when Proc + Chef::Log.info "Running callback #{what} code block" + recipe_eval(&callback_code) + when String + callback_file = "#{release_path}/#{callback_code}" + unless ::File.exist?(callback_file) + raise RuntimeError, "Can't find your callback file #{callback_file}" + end + run_callback_from_file(callback_file) + when nil + run_callback_from_file("#{release_path}/deploy/#{what}.rb") + else + raise RuntimeError, "You gave me a callback I don't know what to do with: #{callback_code.inspect}" + end + end + + def migrate + if @new_resource.migrate + enforce_ownership + link_shared_db_config_to_current_release + + environment = @new_resource.environment + env_info = environment && environment.map do |key_and_val| + "#{key_and_val.first}='#{key_and_val.last}'" + end.join(" ") + + Chef::Log.info "Migrating: running #{@new_resource.migration_command} as #{@new_resource.user} " + + "with environment #{env_info}" + run_command(run_options(:command => @new_resource.migration_command, :cwd=>release_path)) + end + end + + def symlink + Chef::Log.info "Symlinking" + purge_tempfiles_from_current_release + link_tempfiles_to_current_release + link_current_release_to_production + end + + def restart + if restart_cmd = @new_resource.restart_command + if restart_cmd.kind_of?(Proc) + Chef::Log.info("Restarting app with embedded recipe") + recipe_eval(&restart_cmd) + else + Chef::Log.info("Restarting app with #{@new_resource.restart_command} in #{@new_resource.current_path}") + run_command(run_options(:command => @new_resource.restart_command, :cwd => @new_resource.current_path)) + end + end + end + + def cleanup! + all_releases[0..-6].each do |old_release| + Chef::Log.info "Removing old release #{old_release}" + FileUtils.rm_rf(old_release) + release_deleted(old_release) + end + end + + def all_releases + Dir.glob(@new_resource.deploy_to + "/releases/*") + end + + def update_cached_repo + Chef::Log.info "updating the cached checkout" + @scm_provider.action_sync + end + + def copy_cached_repo + Chef::Log.info "copying the cached checkout to #{release_path}" + FileUtils.mkdir_p(@new_resource.deploy_to + "/releases") + FileUtils.cp_r("#{@new_resource.destination}.", release_path, :preserve => true) + release_created(release_path) + end + + def enforce_ownership + Chef::Log.info "ensuring proper ownership" + FileUtils.chown_R(@new_resource.user, @new_resource.group, @new_resource.deploy_to) + end + + def link_current_release_to_production + Chef::Log.info "Linking release #{release_path} into production at #{@new_resource.current_path}" + FileUtils.rm_f(@new_resource.current_path) + FileUtils.ln_sf(release_path, @new_resource.current_path) + enforce_ownership + end + + def link_shared_db_config_to_current_release + links_info = @new_resource.symlink_before_migrate.map { |src, dst| "#{src} => #{dst}" }.join(", ") + Chef::Log.info "Making pre-migration symlinks: #{links_info}" + @new_resource.symlink_before_migrate.each do |src, dest| + FileUtils.ln_sf(@new_resource.shared_path + "/#{src}", release_path + "/#{dest}") + end + end + + def link_tempfiles_to_current_release + dirs_info = @new_resource.create_dirs_before_symlink.join(",") + Chef::Log.info("creating directories before symlink: #{dirs_info}") + @new_resource.create_dirs_before_symlink.each { |dir| FileUtils.mkdir_p(release_path + "/#{dir}") } + + links_info = @new_resource.symlinks.map { |src, dst| "#{src} => #{dst}" }.join(", ") + Chef::Log.info("Linking shared paths into current release: #{links_info}") + @new_resource.symlinks.each do |src, dest| + FileUtils.ln_sf(@new_resource.shared_path + "/#{src}", release_path + "/#{dest}") + end + link_shared_db_config_to_current_release + enforce_ownership + end + + def create_dirs_before_symlink + end + + def purge_tempfiles_from_current_release + log_info = @new_resource.purge_before_symlink.join(", ") + Chef::Log.info("Purging directories in checkout #{log_info}") + @new_resource.purge_before_symlink.each { |dir| FileUtils.rm_rf(release_path + "/#{dir}") } + end + + protected + + # Internal callback, called after copy_cached_repo. + # Override if you need to keep state externally. + def release_created(release_path) + end + + # Internal callback, called during cleanup! for each old release removed. + # Override if you need to keep state externally. + def release_deleted(release_path) + end + + def release_slug + raise Chef::Exceptions::Override, "You must override release_slug in #{self.to_s}" + end + + def install_gems + gems_collection = Chef::ResourceCollection.new + gem_packages.each { |rbgem| gems_collection << rbgem } + Chef::Runner.new(@node, gems_collection).converge + end + + def gem_packages + return [] unless ::File.exist?("#{release_path}/gems.yml") + gems = YAML.load(IO.read("#{release_path}/gems.yml")) + + gems.map do |g| + r = Chef::Resource::GemPackage.new(g[:name], nil, node) + r.version g[:version] + r.action :install + r.source "http://gems.github.com" + r + end + end + + def run_options(run_opts={}) + run_opts[:user] = @new_resource.user if @new_resource.user + run_opts[:group] = @new_resource.group if @new_resource.group + run_opts[:environment] = @new_resource.environment if @new_resource.environment + run_opts + end + + def run_callback_from_file(callback_file) + if ::File.exist?(callback_file) + Dir.chdir(release_path) do + Chef::Log.info "running deploy hook: #{callback_file}" + recipe_eval { from_file(callback_file) } + end + end + end + + end + end +end diff --git a/chef/lib/chef/provider/deploy/revision.rb b/chef/lib/chef/provider/deploy/revision.rb new file mode 100644 index 0000000000..29c3532f42 --- /dev/null +++ b/chef/lib/chef/provider/deploy/revision.rb @@ -0,0 +1,70 @@ +# +# Author:: Daniel DeLeo (<dan@kallistec.com>) +# Copyright:: Copyright (c) 2009 Daniel DeLeo +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +class Chef + class Provider + class Deploy + class Revision < Chef::Provider::Deploy + + def all_releases + sorted_releases + end + + protected + + def release_created(release) + sorted_releases { |r| r << release } + end + + def release_deleted(release) + sorted_releases { |r| r.delete(release)} + end + + def release_slug + scm_provider.revision_slug + end + + private + + def sorted_releases + cache = load_cache + if block_given? + yield cache + save_cache(cache) + end + cache + end + + def load_cache + begin + JSON.parse(Chef::FileCache.load("revision-deploys/#{new_resource.name}")) + rescue + Chef::Exceptions::FileNotFound + save_cache([]) + end + end + + def save_cache(cache) + Chef::FileCache.store("revision-deploys/#{new_resource.name}", cache.to_json) + cache + end + + end + end + end +end
\ No newline at end of file diff --git a/chef/lib/chef/provider/deploy/timestamped.rb b/chef/lib/chef/provider/deploy/timestamped.rb new file mode 100644 index 0000000000..8526d20338 --- /dev/null +++ b/chef/lib/chef/provider/deploy/timestamped.rb @@ -0,0 +1,33 @@ +# +# Author:: Daniel DeLeo (<dan@kallistec.com>) +# Copyright:: Copyright (c) 2009 Daniel DeLeo +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +class Chef + class Provider + class Deploy + class Timestamped < Chef::Provider::Deploy + + protected + + def release_slug + Time.now.utc.strftime("%Y%m%d%H%M%S") + end + + end + end + end +end
\ No newline at end of file diff --git a/chef/lib/chef/provider/git.rb b/chef/lib/chef/provider/git.rb new file mode 100644 index 0000000000..544007c346 --- /dev/null +++ b/chef/lib/chef/provider/git.rb @@ -0,0 +1,194 @@ +# +# Author:: Daniel DeLeo (<dan@kallistec.com>) +# Copyright:: Copyright (c) 2008 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +require 'chef/log' +require 'chef/provider' +require 'chef/mixin/command' +require 'fileutils' + +class Chef + class Provider + class Git < Chef::Provider + + include Chef::Mixin::Command + + def load_current_resource + @current_resource = Chef::Resource::Git.new(@new_resource.name) + if current_revision = find_current_revision + @current_resource.revision current_revision + end + end + + def action_checkout + clone + checkout + enable_submodules + end + + def action_export + action_checkout + FileUtils.rm_rf(::File.join(@new_resource.destination,".git")) + end + + def action_sync + if !::File.exist?(@new_resource.destination) || Dir.entries(@new_resource.destination) == ['.','..'] + action_checkout + else + sync + enable_submodules + end + end + + def find_current_revision + if ::File.exist?(::File.join(cwd, ".git")) + status, result, error_message = output_of_command("git rev-parse HEAD", run_options(:cwd=>cwd)) + + # 128 is returned when we're not in a git repo. this is fine + unless [0,128].include?(status.exitstatus) + handle_command_failures(status, "STDOUT: #{result}\nSTDERR: #{error_message}") + end + end + sha_hash?(result) ? result : nil + end + + def clone + remote = @new_resource.remote + + args = [] + args << "-o #{remote}" unless remote == 'origin' + args << "--depth #{@new_resource.depth}" if @new_resource.depth + + Chef::Log.info "Cloning repo #{@new_resource.repository} to #{@new_resource.destination}" + + clone_cmd = "#{git} clone #{args.join(' ')} #{@new_resource.repository} #{@new_resource.destination}" + run_command(run_options(:command => clone_cmd)) + end + + def checkout + sha_ref = revision_sha + Chef::Log.info "Checking out branch: #{@new_resource.revision} reference: #{sha_ref}" + # checkout into a local branch rather than a detached HEAD + run_command(run_options(:command => "#{git} checkout -b deploy #{sha_ref}", :cwd => @new_resource.destination)) + end + + def enable_submodules + if @new_resource.enable_submodules + Chef::Log.info "Enabling git submodules" + command = "#{git} submodule init && #{git} submodule update" + run_command(run_options(:command => command, :cwd => @new_resource.destination)) + end + end + + def sync + revision = revision_sha + sync_command = [] + + # Use git-config to setup a remote tracking branches. Could use + # git-remote but it complains when a remote of the same name already + # exists, git-config will just silenty overwrite the setting every + # time. This could cause wierd-ness in the remote cache if the url + # changes between calls, but as long as the repositories are all + # based from each other it should still work fine. + if @new_resource.remote != 'origin' + Chef::Log.info "Configuring remote tracking branches for repository #{@new_resource.repository} "+ + "at remote #{@new_resource.remote}" + sync_command << "#{git} config remote.#{@new_resource.remote}.url #{@new_resource.repository}" + sync_command << "#{git} config remote.#{@new_resource.remote}.fetch +refs/heads/*:refs/remotes/#{@new_resource.remote}/*" + end + + # since we're in a local branch already, just reset to specified revision rather than merge + sync_command << "#{git} fetch #{@new_resource.remote} && #{git} reset --hard #{revision}" + Chef::Log.info "Fetching updates from #{new_resource.remote} and resetting to revison #{revision}" + run_command(run_options(:command => sync_command.join(" && "), :cwd => @new_resource.destination)) + end + + def revision_sha + @revision_sha ||= begin + assert_revision_not_remote + + if sha_hash?(@new_resource.revision) + @revision_sha = @new_resource.revision + else + resolved_reference = remote_resolve_reference + @revision_sha = extract_revision(resolved_reference) + end + end + end + + alias :revision_slug :revision_sha + + def remote_resolve_reference + command = scm('ls-remote', @new_resource.repository, @new_resource.revision) + Chef::Log.debug("Executing #{command}") + begin + status, result, error_message = output_of_command(command, run_options) + handle_command_failures(status, "STDOUT: #{result}\nSTDERR: #{error_message}") + rescue RuntimeError => e + raise RuntimeError, e.message + "\n" + "Could not access the remote Git repository. "+ + "If this is a private repository, please verify that the deploy key for your application " + + "has been added to your remote Git account." + end + result + end + + private + + def run_options(run_opts={}) + run_opts[:user] = @new_resource.user if @new_resource.user + run_opts[:environment] = {"GIT_SSH" => @new_resource.ssh_wrapper} if @new_resource.ssh_wrapper + run_opts + end + + def cwd + @new_resource.destination + end + + def scm(*args) + [git, *args].compact.join(" ") + end + + def git + 'git' + end + + def sha_hash?(string) + string =~ /^[0-9a-f]{40}$/ + end + + def assert_revision_not_remote + if @new_resource.revision =~ /^origin\// + reference = @new_resource.revision + error_text = "Deploying remote branches is not supported. " + + "Specify the remote branch as a local branch for " + + "the git repository you're deploying from " + + "(ie: '#{reference.gsub('origin/', '')}' rather than '#{reference}')." + raise RuntimeError, error_text + end + end + + def extract_revision(resolved_reference) + unless resolved_reference =~ /^([0-9a-f]{40})\s+(\S+)/ + raise "Unable to resolve reference for '#{resolved_reference}' on repository '#{@new_resource.repository}'." + end + $1 + end + + end + end +end
\ No newline at end of file diff --git a/chef/lib/chef/provider/group.rb b/chef/lib/chef/provider/group.rb index 30bfcb487c..27500d1e2d 100644 --- a/chef/lib/chef/provider/group.rb +++ b/chef/lib/chef/provider/group.rb @@ -27,8 +27,8 @@ class Chef include Chef::Mixin::Command attr_accessor :group_exists - def initialize(node, new_resource) - super(node, new_resource) + def initialize(node, new_resource, collection=nil, definitions=nil, cookbook_loader=nil) + super(node, new_resource, collection, definitions, cookbook_loader) @group_exists = true end diff --git a/chef/lib/chef/provider/group/gpasswd.rb b/chef/lib/chef/provider/group/gpasswd.rb new file mode 100644 index 0000000000..85b912555a --- /dev/null +++ b/chef/lib/chef/provider/group/gpasswd.rb @@ -0,0 +1,50 @@ +# +# Author:: AJ Christensen (<aj@opscode.com>) +# Copyright:: Copyright (c) 2008 OpsCode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'chef/provider/group/groupadd' + +class Chef + class Provider + class Group + class Gpasswd < Chef::Provider::Group::Groupadd + + def load_current_resource + super + + raise Chef::Exceptions::Group, "Could not find binary /usr/bin/gpasswd for #{@new_resource}" unless ::File.exists?("/usr/bin/gpasswd") + end + + def modify_group_members + unless @new_resource.members.empty? + if(@new_resource.append) + @new_resource.members.each do |member| + Chef::Log.debug("#{@new_resource}: appending member #{member} to group #{@new_resource.group_name}") + run_command(:command => "gpasswd -a #{member} #{@new_resource.group_name}") + end + else + Chef::Log.debug("#{@new_resource}: setting group members to #{@new_resource.members.join(', ')}") + run_command(:command => "gpasswd -M #{@new_resource.members.join(',')} #{@new_resource.group_name}") + end + else + Chef::Log.debug("#{@new_resource}: not changing group members, the group has no members") + end + end + end + end + end +end diff --git a/chef/lib/chef/provider/group/groupadd.rb b/chef/lib/chef/provider/group/groupadd.rb index 54ea73702a..633b0ebf51 100644 --- a/chef/lib/chef/provider/group/groupadd.rb +++ b/chef/lib/chef/provider/group/groupadd.rb @@ -26,8 +26,7 @@ class Chef [ "/usr/sbin/groupadd", "/usr/sbin/groupmod", - "/usr/sbin/groupdel", - "/usr/bin/gpasswd" ].each do |required_binary| + "/usr/sbin/groupdel" ].each do |required_binary| raise Chef::Exceptions::Group, "Could not find binary #{required_binary} for #{@new_resource}" unless ::File.exists?(required_binary) end end @@ -54,21 +53,8 @@ class Chef end def modify_group_members - unless @new_resource.members.empty? - if(@new_resource.append) - @new_resource.members.each do |member| - Chef::Log.debug("#{@new_resource}: appending member #{member} to group #{@new_resource.group_name}") - run_command(:command => "gpasswd -a #{member} #{@new_resource.group_name}") - end - else - Chef::Log.debug("#{@new_resource}: setting group members to #{@new_resource.members.join(', ')}") - run_command(:command => "gpasswd -M #{@new_resource.members.join(',')} #{@new_resource.group_name}") - end - else - Chef::Log.debug("#{@new_resource}: not changing group members, the group has no members") - end + raise Chef::Exceptions::Group, "you must override modify_group_members in #{self.to_s}" end - # Little bit of magic as per Adam's useradd provider to pull the assign the command line flags # # ==== Returns diff --git a/chef/lib/chef/provider/group/usermod.rb b/chef/lib/chef/provider/group/usermod.rb new file mode 100644 index 0000000000..dd2911f34b --- /dev/null +++ b/chef/lib/chef/provider/group/usermod.rb @@ -0,0 +1,57 @@ +# +# Author:: AJ Christensen (<aj@opscode.com>) +# Copyright:: Copyright (c) 2008 OpsCode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require 'chef/provider/group/groupadd' + +class Chef + class Provider + class Group + class Usermod < Chef::Provider::Group::Groupadd + + def load_current_resource + super + + raise Chef::Exceptions::Group, "Could not find binary /usr/sbin/usermod for #{@new_resource}" unless ::File.exists?("/usr/sbin/usermod") + end + + def modify_group_members + case node[:platform] + when "openbsd", "netbsd" + append_flags = "-G" + when "solaris" + append_flags = "-a -G" + end + + unless @new_resource.members.empty? + if(@new_resource.append) + @new_resource.members.each do |member| + Chef::Log.debug("#{@new_resource}: appending member #{member} to group #{@new_resource.group_name}") + run_command(:command => "usermod #{append_flags} #{@new_resource.group_name} #{member}" ) + + end + else + raise Chef::Exceptions::Group, "setting group members directly is not supported by #{self.to_s}" + end + else + Chef::Log.debug("#{@new_resource}: not changing group members, the group has no members") + end + end + end + end + end +end diff --git a/chef/lib/chef/provider/ifconfig.rb b/chef/lib/chef/provider/ifconfig.rb index 30fc41228d..9674e51500 100644 --- a/chef/lib/chef/provider/ifconfig.rb +++ b/chef/lib/chef/provider/ifconfig.rb @@ -106,7 +106,7 @@ class Chef def generate_config b = binding case node[:platform] - when ("centos" || "redhat" || "fedora") + when "centos","redhat","fedora" content = %{ <% if @new_resource.device %>DEVICE=<%= @new_resource.device %><% end %> <% if @new_resource.onboot %>ONBOOT=<%= @new_resource.onboot %><% end %> @@ -120,9 +120,9 @@ class Chef network_file = ::File.new("/etc/sysconfig/network-scripts/ifcfg-#{@new_resource.device}", "w") network_file.puts(template.result(b)) network_file.close - when ("debian" || "ubantu") + when "debian","ubuntu" # template - when ("slackware") + when "slackware" # template end end diff --git a/chef/lib/chef/provider/mount.rb b/chef/lib/chef/provider/mount.rb index a12c298034..404677e6fc 100644 --- a/chef/lib/chef/provider/mount.rb +++ b/chef/lib/chef/provider/mount.rb @@ -26,10 +26,6 @@ class Chef include Chef::Mixin::Command - def initialize(node, new_resource) - super(node, new_resource) - end - def action_mount unless @current_resource.mounted Chef::Log.debug("#{@new_resource}: attempting to mount") diff --git a/chef/lib/chef/provider/mount/mount.rb b/chef/lib/chef/provider/mount/mount.rb index 4de2b54fc6..2012401a17 100644 --- a/chef/lib/chef/provider/mount/mount.rb +++ b/chef/lib/chef/provider/mount/mount.rb @@ -27,8 +27,8 @@ class Chef include Chef::Mixin::Command - def initialize(node, new_resource) - super(node, new_resource) + def initialize(node, new_resource, collection=nil, definitions=nil, cookbook_loader=nil) + super(node, new_resource, collection, definitions, cookbook_loader) @real_device = nil end attr_accessor :real_device diff --git a/chef/lib/chef/provider/package.rb b/chef/lib/chef/provider/package.rb index 41390832a6..3b40135c61 100644 --- a/chef/lib/chef/provider/package.rb +++ b/chef/lib/chef/provider/package.rb @@ -30,8 +30,8 @@ class Chef attr_accessor :candidate_version - def initialize(node, new_resource) - super(node, new_resource) + def initialize(node, new_resource, collection=nil, definitions=nil, cookbook_loader=nil) + super(node, new_resource, collection, definitions, cookbook_loader) @candidate_version = nil end diff --git a/chef/lib/chef/provider/package/apt.rb b/chef/lib/chef/provider/package/apt.rb index a0701db5ed..ae43e76550 100644 --- a/chef/lib/chef/provider/package/apt.rb +++ b/chef/lib/chef/provider/package/apt.rb @@ -61,7 +61,7 @@ class Chef end def install_package(name, version) - run_command( + run_command_with_systems_locale( :command => "apt-get -q -y#{expand_options(@new_resource.options)} install #{name}=#{version}", :environment => { "DEBIAN_FRONTEND" => "noninteractive" @@ -74,7 +74,7 @@ class Chef end def remove_package(name, version) - run_command( + run_command_with_systems_locale( :command => "apt-get -q -y#{expand_options(@new_resource.options)} remove #{@new_resource.package_name}", :environment => { "DEBIAN_FRONTEND" => "noninteractive" @@ -83,7 +83,7 @@ class Chef end def purge_package(name, version) - run_command( + run_command_with_systems_locale( :command => "apt-get -q -y#{expand_options(@new_resource.options)} purge #{@new_resource.package_name}", :environment => { "DEBIAN_FRONTEND" => "noninteractive" @@ -95,7 +95,7 @@ class Chef preseed_file = get_preseed_file(name, version) if preseed_file Chef::Log.info("Pre-seeding #{@new_resource} with package installation instructions.") - run_command( + run_command_with_systems_locale( :command => "debconf-set-selections #{preseed_file}", :environment => { "DEBIAN_FRONTEND" => "noninteractive" diff --git a/chef/lib/chef/provider/package/dpkg.rb b/chef/lib/chef/provider/package/dpkg.rb index 0e4f4d6c32..37f97d2e5c 100644 --- a/chef/lib/chef/provider/package/dpkg.rb +++ b/chef/lib/chef/provider/package/dpkg.rb @@ -40,10 +40,9 @@ class Chef Chef::Log.debug("Checking dpkg status for #{@new_resource.package_name}") status = popen4("dpkg-deb -W #{@new_resource.source}") do |pid, stdin, stdout, stderr| stdout.each do |line| - case line - when /([\w\d]+)\t([\w\d.-]+)/ - @current_resource.package_name($1) - @new_resource.version($2) + if pkginfo = /([a-z\d\-\+]+)\t([\w\d.-]+)/.match(line) + @current_resource.package_name(pkginfo[1]) + @new_resource.version(pkginfo[2]) end end end @@ -79,31 +78,28 @@ class Chef end def install_package(name, version) - run_command( + run_command_with_systems_locale( :command => "dpkg -i#{expand_options(@new_resource.options)} #{@new_resource.source}", :environment => { - "DEBIAN_FRONTEND" => "noninteractive", - "LANG" => "en_US" + "DEBIAN_FRONTEND" => "noninteractive" } ) end def remove_package(name, version) - run_command( + run_command_with_systems_locale( :command => "dpkg -r#{expand_options(@new_resource.options)} #{@new_resource.package_name}", :environment => { - "DEBIAN_FRONTEND" => "noninteractive", - "LANG" => "en_US" + "DEBIAN_FRONTEND" => "noninteractive" } ) end def purge_package(name, version) - run_command( + run_command_with_systems_locale( :command => "dpkg -P#{expand_options(@new_resource.options)} #{@new_resource.package_name}", :environment => { - "DEBIAN_FRONTEND" => "noninteractive", - "LANG" => "en_US" + "DEBIAN_FRONTEND" => "noninteractive" } ) end diff --git a/chef/lib/chef/provider/package/freebsd.rb b/chef/lib/chef/provider/package/freebsd.rb index 5d8f5a40cb..bfe67c8b73 100644 --- a/chef/lib/chef/provider/package/freebsd.rb +++ b/chef/lib/chef/provider/package/freebsd.rb @@ -110,24 +110,24 @@ class Chef unless @current_resource.version case @new_resource.source when /^ports$/ - run_command( + run_command_with_systems_locale( :command => "make -DBATCH install", :cwd => "#{port_path}" ) when /^http/, /^ftp/ - run_command( + run_command_with_systems_locale( :command => "pkg_add -r #{package_name}", :environment => { "PACKAGESITE" => @new_resource.source } ) Chef::Log.info("Installed package #{package_name} from: #{@new_resource.source}") when /^\// - run_command( + run_command_with_systems_locale( :command => "pkg_add #{@new_resource.name}", :environment => { "PKG_PATH" => @new_resource.source } ) Chef::Log.info("Installed package #{@new_resource.name} from: #{@new_resource.source}") else - run_command( + run_command_with_systems_locale( :command => "pkg_add -r #{latest_link_name}" ) Chef::Log.info("Installed package #{package_name}") @@ -138,11 +138,11 @@ class Chef def remove_package(name, version) # a version is mandatory if version - run_command( + run_command_with_systems_locale( :command => "pkg_delete #{package_name}-#{version}" ) else - run_command( + run_command_with_systems_locale( :command => "pkg_delete #{package_name}-#{@current_resource.version}" ) end diff --git a/chef/lib/chef/provider/package/macports.rb b/chef/lib/chef/provider/package/macports.rb index 071d52939d..ca720d4b12 100644 --- a/chef/lib/chef/provider/package/macports.rb +++ b/chef/lib/chef/provider/package/macports.rb @@ -45,7 +45,7 @@ class Chef unless @current_resource.version == version command = "port install #{name}" command << " @#{version}" if version and !version.empty? - run_command( + run_command_with_systems_locale( :command => command ) end @@ -54,7 +54,7 @@ class Chef def purge_package(name, version) command = "port uninstall #{name}" command << " @#{version}" if version and !version.empty? - run_command( + run_command_with_systems_locale( :command => command ) end @@ -63,7 +63,7 @@ class Chef command = "port deactivate #{name}" command << " @#{version}" if version and !version.empty? - run_command( + run_command_with_systems_locale( :command => command ) end @@ -78,7 +78,7 @@ class Chef # that hasn't been installed. install_package(name, version) elsif current_version != version - run_command( + run_command_with_systems_locale( :command => "port upgrade #{name} @#{version}" ) end diff --git a/chef/lib/chef/provider/package/portage.rb b/chef/lib/chef/provider/package/portage.rb index 8b0b00b6bc..9211e6a445 100644 --- a/chef/lib/chef/provider/package/portage.rb +++ b/chef/lib/chef/provider/package/portage.rb @@ -93,7 +93,7 @@ class Chef pkg = "~#{name}-#{$1}" end - run_command( + run_command_with_systems_locale( :command => "emerge -g --color n --nospinner --quiet#{expand_options(@new_resource.options)} #{pkg}" ) end @@ -109,7 +109,7 @@ class Chef pkg = "#{@new_resource.package_name}" end - run_command( + run_command_with_systems_locale( :command => "emerge --unmerge --color n --nospinner --quiet#{expand_options(@new_resource.options)} #{pkg}" ) end diff --git a/chef/lib/chef/provider/package/rpm.rb b/chef/lib/chef/provider/package/rpm.rb index ab39f47573..2257b14592 100644 --- a/chef/lib/chef/provider/package/rpm.rb +++ b/chef/lib/chef/provider/package/rpm.rb @@ -69,24 +69,24 @@ class Chef end def install_package(name, version) - run_command( + run_command_with_systems_locale( :command => "rpm -i #{@new_resource.source}" ) end def upgrade_package(name, version) - run_command( + run_command_with_systems_locale( :command => "rpm -U #{@new_resource.source}" ) end def remove_package(name, version) if version - run_command( + run_command_with_systems_locale( :command => "rpm -e #{name}-#{version}" ) else - run_command( + run_command_with_systems_locale( :command => "rpm -e #{name}" ) end diff --git a/chef/lib/chef/provider/package/rubygems.rb b/chef/lib/chef/provider/package/rubygems.rb index c508923aad..3502692ef6 100644 --- a/chef/lib/chef/provider/package/rubygems.rb +++ b/chef/lib/chef/provider/package/rubygems.rb @@ -99,7 +99,7 @@ class Chef if @new_resource.source src = " --source=#{@new_resource.source} --source=http://gems.rubyforge.org" end - run_command( + run_command_with_systems_locale( :command => "#{gem_binary_path} install #{name} -q --no-rdoc --no-ri -v \"#{version}\"#{src}" ) end @@ -110,11 +110,11 @@ class Chef def remove_package(name, version) if version - run_command( + run_command_with_systems_locale( :command => "#{gem_binary_path} uninstall #{name} -q -v \"#{version}\"" ) else - run_command( + run_command_with_systems_locale( :command => "#{gem_binary_path} uninstall #{name} -q -a" ) end diff --git a/chef/lib/chef/provider/package/yum.rb b/chef/lib/chef/provider/package/yum.rb index efd6fee0ad..ac05fefefa 100644 --- a/chef/lib/chef/provider/package/yum.rb +++ b/chef/lib/chef/provider/package/yum.rb @@ -106,9 +106,9 @@ class Chef end end - def initialize(node, new_resource) + def initialize(node, new_resource, collection=nil, definitions=nil, cookbook_loader=nil) @yum = YumCache.instance - super(node, new_resource) + super(node, new_resource, collection, definitions, cookbook_loader) end def load_current_resource @@ -133,7 +133,7 @@ class Chef end def install_package(name, version) - run_command( + run_command_with_systems_locale( :command => "yum -q -y install #{name}-#{version}" ) @yum.flush @@ -142,7 +142,7 @@ class Chef def upgrade_package(name, version) # If we have a version, we can upgrade - otherwise, install if @current_resource.version - run_command( + run_command_with_systems_locale( :command => "yum -q -y update #{name}-#{version}" ) @yum.flush @@ -153,11 +153,11 @@ class Chef def remove_package(name, version) if version - run_command( + run_command_with_systems_locale( :command => "yum -q -y remove #{name}-#{version}" ) else - run_command( + run_command_with_systems_locale( :command => "yum -q -y remove #{name}" ) end diff --git a/chef/lib/chef/provider/remote_file.rb b/chef/lib/chef/provider/remote_file.rb index 9b5c9db04a..247549ed95 100644 --- a/chef/lib/chef/provider/remote_file.rb +++ b/chef/lib/chef/provider/remote_file.rb @@ -55,23 +55,30 @@ class Chef 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} changed from #{@current_resource.checksum} to #{@new_resource.checksum}") - Chef::Log.info("Updating #{@new_resource} at #{@new_resource.path}") - backup(@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 + 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("Creating #{@new_resource} at #{@new_resource.path}") + Chef::Log.info "#{@new_resource}: Creating #{@new_resource.path}" + FileUtils.cp raw_file.path, @new_resource.path + @new_resource.updated = true end - FileUtils.cp(raw_file.path, @new_resource.path) - @new_resource.updated = true - # 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 diff --git a/chef/lib/chef/provider/service.rb b/chef/lib/chef/provider/service.rb index f35c424174..bea4d03723 100644 --- a/chef/lib/chef/provider/service.rb +++ b/chef/lib/chef/provider/service.rb @@ -25,8 +25,8 @@ class Chef include Chef::Mixin::Command - def initialize(node, new_resource) - super(node, new_resource) + def initialize(node, new_resource, collection=nil, definitions=nil, cookbook_loader=nil) + super(node, new_resource, collection, definitions, cookbook_loader) @enabled = nil end @@ -35,6 +35,7 @@ class Chef Chef::Log.debug("#{@new_resource}: attempting to enable") status = enable_service() if status + @new_resource.updated = true Chef::Log.info("#{@new_resource}: enabled successfully") end else @@ -47,6 +48,7 @@ class Chef Chef::Log.debug("#{@new_resource}: attempting to disable") status = disable_service() if status + @new_resource.updated = true Chef::Log.info("#{@new_resource}: disabled successfully") end else @@ -59,6 +61,7 @@ class Chef Chef::Log.debug("#{@new_resource}: attempting to start") status = start_service() if status + @new_resource.updated = true Chef::Log.info("Started service #{@new_resource} successfully") end else @@ -71,6 +74,7 @@ class Chef Chef::Log.debug("#{@new_resource}: attempting to stop") status = stop_service() if status + @new_resource.updated = true Chef::Log.info("#{@new_resource}: stopped successfully") end else @@ -82,6 +86,7 @@ class Chef Chef::Log.debug("#{@new_resource}: attempting to restart") status = restart_service() if status + @new_resource.updated = true Chef::Log.info("#{@new_resource}: restarted successfully") end end @@ -94,6 +99,7 @@ class Chef Chef::Log.debug("#{@new_resource}: attempting to reload") status = reload_service() if status + @new_resource.updated = true Chef::Log.info("#{@new_resource}: reloaded successfully") end end diff --git a/chef/lib/chef/provider/service/init.rb b/chef/lib/chef/provider/service/init.rb index 39f3a63ee7..95709626dd 100644 --- a/chef/lib/chef/provider/service/init.rb +++ b/chef/lib/chef/provider/service/init.rb @@ -25,8 +25,8 @@ class Chef class Service class Init < Chef::Provider::Service::Simple - def initialize(node, new_resource) - super(node, new_resource) + def initialize(node, new_resource, collection=nil, definitions=nil, cookbook_loader=nil) + super(node, new_resource, collection, definitions, cookbook_loader) @init_command = "/etc/init.d/#{@new_resource.service_name}" end diff --git a/chef/lib/chef/provider/service/redhat.rb b/chef/lib/chef/provider/service/redhat.rb index 64bfdc4076..ab52762a39 100644 --- a/chef/lib/chef/provider/service/redhat.rb +++ b/chef/lib/chef/provider/service/redhat.rb @@ -25,8 +25,8 @@ class Chef class Service class Redhat < Chef::Provider::Service::Init - def initialize(node, new_resource) - super(node, new_resource) + def initialize(node, new_resource, collection=nil, definitions=nil, cookbook_loader=nil) + super(node, new_resource, collection, definitions, cookbook_loader) @init_command = "/sbin/service #{@new_resource.service_name}" end diff --git a/chef/lib/chef/provider/subversion.rb b/chef/lib/chef/provider/subversion.rb new file mode 100644 index 0000000000..c6d118474d --- /dev/null +++ b/chef/lib/chef/provider/subversion.rb @@ -0,0 +1,145 @@ +# +# Author:: Daniel DeLeo (<dan@kallistec.com>) +# Copyright:: Copyright (c) 2008 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +require 'chef/log' +require 'chef/provider' +require 'chef/mixin/command' +require 'fileutils' + +class Chef + class Provider + class Subversion < Chef::Provider + + include Chef::Mixin::Command + + def load_current_resource + @current_resource = Chef::Resource::Subversion.new(@new_resource.name) + if current_revision = find_current_revision + @current_resource.revision current_revision + end + end + + def action_checkout + run_command(run_options(:command => checkout_command)) + end + + def action_export + run_command(run_options(:command => export_command)) + end + + def action_sync + if !::File.exist?(@new_resource.destination + "/.svn") || ::Dir.entries(@new_resource.destination) == ['.','..'] + action_checkout + else + run_command(run_options(:command => sync_command)) + end + end + + def sync_command + Chef::Log.info "Updating working copy #{@new_resource.destination} to revision #{@new_resource.revision}" + scm :update, @new_resource.svn_arguments, verbose, authentication, "-r#{revision_int}", @new_resource.destination + end + + def checkout_command + Chef::Log.info "checking out #{@new_resource.repository} at revision #{@new_resource.revision} to #{@new_resource.destination}" + scm :checkout, @new_resource.svn_arguments, verbose, authentication, + "-r#{revision_int}", @new_resource.repository, @new_resource.destination + end + + def export_command + Chef::Log.info "exporting #{@new_resource.repository} at revision #{@new_resource.revision} to #{@new_resource.destination}" + scm :export, @new_resource.svn_arguments, verbose, authentication, + "-r#{revision_int}", @new_resource.repository, @new_resource.destination + end + + # If the specified revision isn't an integer ("HEAD" for example), look + # up the revision id by asking the server + # If the specified revision is an integer, trust it. + def revision_int + @revision_int ||= begin + if @new_resource.revision =~ /^\d+$/ + @new_resource.revision + else + command = scm(:info, @new_resource.repository, 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) + end + end + end + + alias :revision_slug :revision_int + + def find_current_revision + return nil unless ::File.exist?(@new_resource.destination) + command = scm(:info) + status, svn_info, error_message = output_of_command(command, run_options(:cwd => cwd)) + + unless [0,1].include?(status.exitstatus) + handle_command_failures(status, "STDOUT: #{svn_info}\nSTDERR: #{error_message}") + end + extract_revision_info(svn_info) + end + + def run_options(run_opts={}) + run_opts[:user] = @new_resource.user if @new_resource.user + run_opts + end + + private + + def cwd + @new_resource.destination + end + + def verbose + "-q" + end + + def extract_revision_info(svn_info) + begin + repo_attrs = YAML.load(svn_info) + rescue ArgumentError + # YAML doesn't appreciate input like "svn: '/tmp/deploydir' is not a working copy\n" + return nil + end + raise "tried to run `#{command}' and got unexpected result #{result.inspect}" unless repo_attrs.kind_of?(Hash) + rev = (repo_attrs['Last Changed Rev'] || repo_attrs['Revision']).to_s + Chef::Log.debug "Resolved revision #{@new_resource.revision} to #{rev}" + rev + end + + # If a username is configured for the SCM, return the command-line + # switches for that. Note that we don't need to return the password + # switch, since Capistrano will check for that prompt in the output + # and will respond appropriately. + def authentication + return "" unless @new_resource.svn_username + result = "--username #{@new_resource.svn_username} " + result << "--password #{@new_resource.svn_password} " + result + end + + def scm(*args) + ['svn', *args].compact.join(" ") + end + + end + end +end diff --git a/chef/lib/chef/provider/template.rb b/chef/lib/chef/provider/template.rb index aa840be43d..4269688a85 100644 --- a/chef/lib/chef/provider/template.rb +++ b/chef/lib/chef/provider/template.rb @@ -38,6 +38,8 @@ class Chef cookbook_name = @new_resource.cookbook || @new_resource.cookbook_name + 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}" diff --git a/chef/lib/chef/provider/user.rb b/chef/lib/chef/provider/user.rb index 3f76f8ad4b..5bff74d49b 100644 --- a/chef/lib/chef/provider/user.rb +++ b/chef/lib/chef/provider/user.rb @@ -29,8 +29,8 @@ class Chef attr_accessor :user_exists, :locked - def initialize(node, new_resource) - super(node, new_resource) + def initialize(node, new_resource, collection=nil, definitions=nil, cookbook_loader=nil) + super(node, new_resource, collection, definitions, cookbook_loader) @user_exists = true @locked = nil end diff --git a/chef/lib/chef/recipe.rb b/chef/lib/chef/recipe.rb index d3e64d0737..86d65d8284 100644 --- a/chef/lib/chef/recipe.rb +++ b/chef/lib/chef/recipe.rb @@ -1,6 +1,7 @@ # # Author:: Adam Jacob (<adam@opscode.com>) -# Copyright:: Copyright (c) 2008 Opscode, Inc. +# Author:: Christopher Walters (<cw@opscode.com>) +# Copyright:: Copyright (c) 2008, 2009 Opscode, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,6 +21,7 @@ require 'chef/resource' Dir[File.join(File.dirname(__FILE__), 'resource/**/*.rb')].sort.each { |lib| require lib } require 'chef/mixin/from_file' require 'chef/mixin/language' +require 'chef/mixin/recipe_definition_dsl_core' require 'chef/resource_collection' require 'chef/cookbook_loader' require 'chef/rest' @@ -29,7 +31,8 @@ class Chef include Chef::Mixin::FromFile include Chef::Mixin::Language - + include Chef::Mixin::RecipeDefinitionDSLCore + attr_accessor :cookbook_name, :recipe_name, :recipe, :node, :collection, :definitions, :params, :cookbook_loader @@ -37,25 +40,9 @@ class Chef @cookbook_name = cookbook_name @recipe_name = recipe_name @node = node - - if collection - @collection = collection - else - @collection = Chef::ResourceCollection.new() - end - - if definitions - @definitions = definitions - else - @definitions = Hash.new - end - - if cookbook_loader - @cookbook_loader = cookbook_loader - else - @cookbook_loader = Chef::CookbookLoader.new() - end - + @collection = collection || Chef::ResourceCollection.new + @definitions = definitions || Hash.new + @cookbook_loader = cookbook_loader || Chef::CookbookLoader.new @params = Hash.new end @@ -136,59 +123,6 @@ class Chef @node[:tags].delete(tag) end end - - def method_missing(method_symbol, *args, &block) - resource = nil - # If we have a definition that matches, we want to use that instead. This should - # let you do some really crazy over-riding of "native" types, if you really want - # to. - if @definitions.has_key?(method_symbol) - # This dupes the high level object, but we still need to dup the params - new_def = @definitions[method_symbol].dup - new_def.params = new_def.params.dup - new_def.node = @node - # This sets up the parameter overrides - new_def.instance_eval(&block) if block - new_recipe = Chef::Recipe.new(@cookbook_name, @recipe_name, @node, @collection, @definitions, @cookbook_loader) - new_recipe.params = new_def.params - new_recipe.params[:name] = args[0] - new_recipe.instance_eval(&new_def.recipe) - else - method_name = method_symbol.to_s - # Otherwise, we're rocking the regular resource call route. - rname = nil - regexp = %r{^(.+?)(_(.+))?$} - - mn = method_name.match(regexp) - if mn - rname = "Chef::Resource::#{mn[1].capitalize}" - - while mn && mn[3] - mn = mn[3].match(regexp) - rname << mn[1].capitalize if mn - end - end - - begin - args << @collection - args << @node - resource = eval(rname).new(*args) - # If we have a resource like this one, we want to steal it's state - resource.load_prior_resource - resource.cookbook_name = @cookbook_name - resource.recipe_name = @recipe_name - resource.params = @params - resource.instance_eval(&block) if block - rescue Exception => e - if e.kind_of?(NameError) && e.to_s =~ /Chef::Resource/ - raise NameError, "Cannot find #{rname} for #{method_name}\nOriginal: #{e.to_s}" - else - raise e - end - end - @collection << resource - resource - end - end + end end diff --git a/chef/lib/chef/resource.rb b/chef/lib/chef/resource.rb index e1b2796632..3a85639295 100644 --- a/chef/lib/chef/resource.rb +++ b/chef/lib/chef/resource.rb @@ -1,5 +1,6 @@ # # Author:: Adam Jacob (<adam@opscode.com>) +# Author:: Christopher Walters (<cw@opscode.com>) # Copyright:: Copyright (c) 2008 Opscode, Inc. # License:: Apache License, Version 2.0 # @@ -19,6 +20,7 @@ require 'chef/mixin/params_validate' require 'chef/mixin/check_helper' require 'chef/mixin/language' +require 'chef/mixin/convert_to_class_name' require 'chef/resource_collection' require 'chef/node' @@ -28,8 +30,9 @@ class Chef include Chef::Mixin::CheckHelper include Chef::Mixin::ParamsValidate include Chef::Mixin::Language + include Chef::Mixin::ConvertToClassName - attr_accessor :actions, :params, :provider, :updated, :allowed_actions, :collection, :cookbook_name, :recipe_name + attr_accessor :actions, :params, :provider, :updated, :allowed_actions, :collection, :cookbook_name, :recipe_name, :enclosing_provider attr_reader :resource_name, :source_line, :node def initialize(name, collection=nil, node=nil) @@ -58,6 +61,17 @@ class Chef @source_line = ::File.expand_path(@source_line) if @source_line end end + + # If an unknown method is invoked, determine whether the enclosing Provider's + # lexical scope can fulfill the request. E.g. This happens when the Resource's + # block invokes new_resource. + def method_missing(method_symbol, *args, &block) + if enclosing_provider && enclosing_provider.respond_to?(method_symbol) + enclosing_provider.send(method_symbol, *args, &block) + else + raise NoMethodError, "undefined method `#{method_symbol.to_s}' for #{self.class.to_s}" + end + end def load_prior_resource begin @@ -83,9 +97,14 @@ class Chef end def provider(arg=nil) + klass = if arg.kind_of?(String) || arg.kind_of?(Symbol) + lookup_provider_constant(arg) + else + arg + end set_or_return( :provider, - arg, + klass, :kind_of => [ Class ] ) end @@ -192,12 +211,12 @@ class Chef results.to_json(*a) end - def self.json_create(o) - resource = self.new(o["instance_vars"]["@name"]) - o["instance_vars"].each do |k,v| - resource.instance_variable_set(k.to_sym, v) + 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" end - resource + instance_vars end def only_if(arg=nil, &blk) @@ -224,7 +243,101 @@ class Chef provider.send("action_#{action}") end + class << self + + def json_create(o) + resource = self.new(o["instance_vars"]["@name"]) + o["instance_vars"].each do |k,v| + resource.instance_variable_set(k.to_sym, v) + end + resource + end + + include Chef::Mixin::ConvertToClassName + + def attribute(attr_name, validation_opts={}) + define_method(attr_name.to_sym) do |arg| + set_or_return(attr_name.to_sym, arg, validation_opts) + end + end + + def build_from_file(cookbook_name, filename) + rname = filename_to_qualified_string(cookbook_name, filename) + + new_resource_class = Class.new self do |cls| + + # default initialize method that ensures that when initialize is finally + # wrapped (see below), super is called in the event that the resource + # definer does not implement initialize + def initialize(name, collection=nil, node=nil) + super(name, collection, node) + end + + @actions_to_create = [] + + class << cls + include Chef::Mixin::FromFile + + def actions_to_create + @actions_to_create + end + + define_method(:actions) do |*action_names| + actions_to_create.push(*action_names) + end + end + + # load resource definition from file + cls.class_from_file(filename) + + # create a new constructor that wraps the old one and adds the actions + # specified in the DSL + old_init = instance_method(:initialize) + + define_method(:initialize) do |name, *optional_args| + collection = optional_args.shift + node = optional_args.shift + @resource_name = rname.to_sym + old_init.bind(self).call(name, collection, node) + allowed_actions.push(self.class.actions_to_create).flatten! + end + end + + # register new class as a Chef::Resource + class_name = convert_to_class_name(rname) + Chef::Resource.const_set(class_name, new_resource_class) + Chef::Log.debug("Loaded contents of #{filename} into a resource named #{rname} defined in Chef::Resource::#{class_name}") + + new_resource_class + end + + # Resources that want providers namespaced somewhere other than + # Chef::Provider can set the namespace with +provider_base+ + # Ex: + # class MyResource < Chef::Resource + # provider_base Chef::Provider::Deploy + # # ...other stuff + # end + def provider_base(arg=nil) + @provider_base ||= arg + @provider_base ||= Chef::Provider + end + + end + private + + def lookup_provider_constant(name) + begin + self.class.provider_base.const_get(convert_to_class_name(name.to_s)) + rescue NameError => e + if e.to_s =~ /#{self.class.provider_base.to_s}/ + raise ArgumentError, "No provider found to match '#{name}'" + else + raise e + end + end + end def check_timing(timing) unless timing == :delayed || timing == :immediate || timing == :immediately diff --git a/chef/lib/chef/resource/cron.rb b/chef/lib/chef/resource/cron.rb index 9e6a03762f..2da76b62f8 100644 --- a/chef/lib/chef/resource/cron.rb +++ b/chef/lib/chef/resource/cron.rb @@ -34,6 +34,10 @@ class Chef @weekday = "*" @command = nil @user = "root" + @mailto = nil + @path = nil + @shell = nil + @home = nil end def minute(arg=nil) @@ -121,6 +125,38 @@ class Chef ) end + def mailto(arg=nil) + set_or_return( + :mailto, + arg, + :kind_of => String + ) + end + + def path(arg=nil) + set_or_return( + :path, + arg, + :kind_of => String + ) + end + + def home(arg=nil) + set_or_return( + :home, + arg, + :kind_of => String + ) + end + + def shell(arg=nil) + set_or_return( + :shell, + arg, + :kind_of => String + ) + end + def command(arg=nil) set_or_return( :command, diff --git a/chef/lib/chef/resource/deploy.rb b/chef/lib/chef/resource/deploy.rb new file mode 100644 index 0000000000..a2fa41e09a --- /dev/null +++ b/chef/lib/chef/resource/deploy.rb @@ -0,0 +1,360 @@ +# +# Author:: Daniel DeLeo (<dan@kallistec.com>) +# Copyright:: Copyright (c) 2008 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# EX: +# deploy "/my/deploy/dir" do +# repo "git@github.com/whoami/project" +# revision "abc123" # or "HEAD" or "TAG_for_1.0" or (subversion) "1234" +# user "deploy_ninja" +# enable_submodules true +# migrate true +# migration_command "rake db:migrate" +# environment "RAILS_ENV" => "production", "OTHER_ENV" => "foo" +# shallow_clone true +# action :deploy # or :rollback +# restart_command "touch tmp/restart.txt" +# git_ssh_wrapper "wrap-ssh4git.sh" +# scm_provider Chef::Provider::Git # is the default, for svn: Chef::Provider::Subversion +# svn_username "whoami" +# svn_password "supersecret" +# end + +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 + # 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 + @deploy_to = name + @environment = nil + @repository_cache = 'cached-copy' + @copy_exclude = [] + @purge_before_symlink = %w{log tmp/pids public/system} + @create_dirs_before_symlink = %w{tmp public config} + @symlink_before_migrate = {"config/database.yml" => "config/database.yml"} + @symlinks = {"system" => "public/system", "pids" => "tmp/pids", "log" => "log"} + @revision = 'HEAD' + @action = :deploy + @migrate = false + @remote = "origin" + @enable_submodules = false + @shallow_clone = false + @force_deploy = false + @scm_provider = Chef::Provider::Git + @provider = Chef::Provider::Deploy::Timestamped + @allowed_actions.push(: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( + :deploy_to, + arg, + :kind_of => [ String ] + ) + end + + def repo(arg=nil) + set_or_return( + :repo, + arg, + :kind_of => [ String ] + ) + end + alias :repository :repo + + def remote(arg=nil) + set_or_return( + :remote, + arg, + :kind_of => [ String ] + ) + end + + def role(arg=nil) + set_or_return( + :role, + arg, + :kind_of => [ String ] + ) + end + + def restart_command(arg=nil, &block) + arg ||= block + set_or_return( + :restart_command, + arg, + :kind_of => [ String, Proc ] + ) + end + alias :restart :restart_command + + def migrate(arg=nil) + set_or_return( + :migrate, + arg, + :kind_of => [ TrueClass, FalseClass ] + ) + end + + def migration_command(arg=nil) + set_or_return( + :migration_command, + arg, + :kind_of => [ String ] + ) + end + + def user(arg=nil) + set_or_return( + :user, + arg, + :kind_of => [ String ] + ) + end + + def group(arg=nil) + set_or_return( + :group, + arg, + :kind_of => [ String ] + ) + end + + def enable_submodules(arg=nil) + set_or_return( + :enable_submodules, + arg, + :kind_of => [ TrueClass, FalseClass ] + ) + end + + def shallow_clone(arg=nil) + set_or_return( + :shallow_clone, + arg, + :kind_of => [ TrueClass, FalseClass ] + ) + end + + def repository_cache(arg=nil) + set_or_return( + :repository_cache, + arg, + :kind_of => [ String ] + ) + end + + def copy_exclude(arg=nil) + set_or_return( + :copy_exclude, + arg, + :kind_of => [ String ] + ) + end + + def revision(arg=nil) + set_or_return( + :revision, + arg, + :kind_of => [ String ] + ) + end + alias :branch :revision + + def git_ssh_wrapper(arg=nil) + set_or_return( + :git_ssh_wrapper, + arg, + :kind_of => [ String ] + ) + end + alias :ssh_wrapper :git_ssh_wrapper + + def svn_username(arg=nil) + set_or_return( + :svn_username, + arg, + :kind_of => [ String ] + ) + end + + def svn_password(arg=nil) + set_or_return( + :svn_password, + arg, + :kind_of => [ String ] + ) + end + + def svn_arguments(arg=nil) + set_or_return( + :svn_arguments, + arg, + :kind_of => [ String ] + ) + end + + # Shall we run the deploy even if the code has not changed? + def force_deploy(arg=nil) + set_or_return( + :force_deploy, + arg, + :kind_of => [ TrueClass, FalseClass ] + ) + end + + def scm_provider(arg=nil) + set_or_return( + :scm_provider, + arg, + :kind_of => [ Class ] + ) + end + + def environment(arg=nil) + if arg.is_a?(String) + Chef::Log.info "Setting RAILS_ENV, RACK_ENV, and MERB_ENV to `#{arg}'" + Chef::Log.warn "[DEPRECATED] please modify your deploy recipe or attributes to set the environment using a hash" + arg = {"RAILS_ENV"=>arg,"MERB_ENV"=>arg,"RACK_ENV"=>arg} + end + set_or_return( + :environment, + arg, + :kind_of => [ Hash ] + ) + end + + # 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"] + def purge_before_symlink(arg=nil) + set_or_return( + :purge_before_symlink, + arg, + :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, + # 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"] + def create_dirs_before_symlink(arg=nil) + set_or_return( + :create_dirs_before_symlink, + arg, + :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 + # $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"} + def symlinks(arg=nil) + set_or_return( + :symlinks, + arg, + :kind_of => Hash + ) + end + + # 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. + # For a rails/merb app, this is used to link in a known good database.yml + # (with the production db password) before running migrate. + # Default {"config/database.yml" => "config/database.yml"} + def symlink_before_migrate(arg=nil) + set_or_return( + :symlink_before_migrate, + arg, + :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
\ No newline at end of file diff --git a/chef/lib/chef/resource/deploy_revision.rb b/chef/lib/chef/resource/deploy_revision.rb new file mode 100644 index 0000000000..499a163ed9 --- /dev/null +++ b/chef/lib/chef/resource/deploy_revision.rb @@ -0,0 +1,35 @@ +# +# Author:: Daniel DeLeo (<dan@kallistec.com>) +# Copyright:: Copyright (c) 2009 Daniel DeLeo +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +class Chef + class Resource + + # Convenience class for using the deploy resource with the revision + # deployment strategy (provider) + class DeployRevision < Chef::Resource::Deploy + def initialize(*args, &block) + super + @provider = Chef::Provider::Deploy::Revision + end + end + + class DeployBranch < Chef::Resource::DeployRevision + end + + end +end
\ No newline at end of file diff --git a/chef/lib/chef/resource/git.rb b/chef/lib/chef/resource/git.rb new file mode 100644 index 0000000000..b95982eabc --- /dev/null +++ b/chef/lib/chef/resource/git.rb @@ -0,0 +1,36 @@ +# +# Author:: Daniel DeLeo (<dan@kallistec.com>) +# Copyright:: Copyright (c) 2008 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require "chef/resource/scm" + +class Chef + class Resource + class Git < Chef::Resource::Scm + + def initialize(name, collection=nil, node=nil) + super(name, collection, node) + @resource_name = :git + @provider = Chef::Provider::Git + end + + alias :branch :revision + alias :reference :revision + + end + end +end
\ No newline at end of file diff --git a/chef/lib/chef/resource/group.rb b/chef/lib/chef/resource/group.rb index 25d8598f42..36a6c141d4 100644 --- a/chef/lib/chef/resource/group.rb +++ b/chef/lib/chef/resource/group.rb @@ -55,6 +55,8 @@ class Chef :kind_of => [ Array ] ) end + + alias_method :users, :members def append(arg=nil) set_or_return( diff --git a/chef/lib/chef/resource/scm.rb b/chef/lib/chef/resource/scm.rb new file mode 100644 index 0000000000..5e3ae973fb --- /dev/null +++ b/chef/lib/chef/resource/scm.rb @@ -0,0 +1,129 @@ +# +# Author:: Daniel DeLeo (<dan@kallistec.com>) +# Copyright:: Copyright (c) 2008 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +require 'chef/resource' + +class Chef + class Resource + class Scm < Chef::Resource + + def initialize(name, collection=nil, node=nil) + super(name, collection, node) + @destination = name + @resource_name = :scm + @enable_submodules = false + @revision = "HEAD" + @remote = "origin" + @ssh_wrapper = nil + @depth = nil + @allowed_actions.push(:checkout, :export, :sync, :diff, :log) + end + + def destination(arg=nil) + set_or_return( + :destination, + arg, + :kind_of => String + ) + end + + def repository(arg=nil) + set_or_return( + :repository, + arg, + :kind_of => String + ) + end + + def revision(arg=nil) + set_or_return( + :revision, + arg, + :kind_of => String + ) + end + + def user(arg=nil) + set_or_return( + :user, + arg, + :kind_of => [String, Integer] + ) + end + + def svn_username(arg=nil) + set_or_return( + :svn_username, + arg, + :kind_of => String + ) + end + + def svn_password(arg=nil) + set_or_return( + :svn_password, + arg, + :kind_of => String + ) + end + + def svn_arguments(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( + :depth, + arg, + :kind_of => Integer + ) + end + + def enable_submodules(arg=nil) + set_or_return( + :enable_submodules, + arg, + :kind_of => [TrueClass, FalseClass] + ) + end + + def remote(arg=nil) + set_or_return( + :remote, + arg, + :kind_of => String + ) + end + + def ssh_wrapper(arg=nil) + set_or_return( + :ssh_wrapper, + arg, + :kind_of => String + ) + end + + end + end +end
\ No newline at end of file diff --git a/chef/lib/chef/resource/subversion.rb b/chef/lib/chef/resource/subversion.rb new file mode 100644 index 0000000000..79cb67fe85 --- /dev/null +++ b/chef/lib/chef/resource/subversion.rb @@ -0,0 +1,33 @@ +# +# Author:: Daniel DeLeo (<dan@kallistec.com>) +# Copyright:: Copyright (c) 2008 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require "chef/resource/scm" + +class Chef + class Resource + class Subversion < Chef::Resource::Scm + + def initialize(name, collection=nil, node=nil) + super(name, collection, node) + @resource_name = :subversion + @provider = Chef::Provider::Subversion + end + + end + end +end diff --git a/chef/lib/chef/resource/timestamped_deploy.rb b/chef/lib/chef/resource/timestamped_deploy.rb new file mode 100644 index 0000000000..d89274bb44 --- /dev/null +++ b/chef/lib/chef/resource/timestamped_deploy.rb @@ -0,0 +1,31 @@ +# +# Author:: Daniel DeLeo (<dan@kallistec.com>) +# Copyright:: Copyright (c) 2009 Daniel DeLeo +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +class Chef + class Resource + + # Convenience class for using the deploy resource with the timestamped + # deployment strategy (provider) + class TimestampedDeploy < Chef::Resource::Deploy + def initialize(*args, &block) + super(*args, &block) + @provider = Chef::Provider::Deploy::Timestamped + end + end + end +end diff --git a/chef/lib/chef/resource_collection.rb b/chef/lib/chef/resource_collection.rb index 8a6655b704..7681e630a6 100644 --- a/chef/lib/chef/resource_collection.rb +++ b/chef/lib/chef/resource_collection.rb @@ -1,6 +1,7 @@ # # Author:: Adam Jacob (<adam@opscode.com>) -# Copyright:: Copyright (c) 2008 Opscode, Inc. +# Author:: Christopher Walters (<cw@opscode.com>) +# Copyright:: Copyright (c) 2008, 2009 Opscode, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -21,10 +22,11 @@ require 'chef/resource' class Chef class ResourceCollection include Enumerable - + def initialize @resources = Array.new @resources_by_name = Hash.new + @insert_after_idx = nil end def [](index) @@ -36,7 +38,7 @@ class Chef @resources[index] = arg @resources_by_name[arg.to_s] = index end - + def <<(*args) args.flatten.each do |a| is_chef_resource(a) @@ -44,6 +46,25 @@ class Chef @resources_by_name[a.to_s] = @resources.length - 1 end end + + def insert(resource) + is_chef_resource(resource) + if @insert_after_idx + # in the middle of executing a run, so any resources inserted now should + # be placed after the most recent addition done by the currently executing + # resource + @resources.insert(@insert_after_idx + 1, resource) + # update name -> location mappings and register new resource + @resources_by_name.each_key do |key| + @resources_by_name[key] += 1 if @resources_by_name[key] > @insert_after_idx + end + @resources_by_name[resource.to_s] = @insert_after_idx + 1 + @insert_after_idx += 1 + else + @resources << resource + @resources_by_name[resource.to_s] = @resources.length - 1 + end + end def push(*args) args.flatten.each do |a| @@ -58,6 +79,13 @@ class Chef yield r end end + + def execute_each_resource + @resources.each_with_index do |r, idx| + @insert_after_idx = idx + yield r + end + end def each_index @resources.each_index do |i| @@ -173,4 +201,4 @@ class Chef true end end -end
\ No newline at end of file +end diff --git a/chef/lib/chef/runner.rb b/chef/lib/chef/runner.rb index f1742dd991..2bbef01f40 100644 --- a/chef/lib/chef/runner.rb +++ b/chef/lib/chef/runner.rb @@ -26,7 +26,7 @@ class Chef include Chef::Mixin::ParamsValidate - def initialize(node, collection) + def initialize(node, collection, definitions={}, cookbook_loader=nil) validate( { :node => node, @@ -43,22 +43,49 @@ class Chef ) @node = node @collection = collection + @definitions = definitions + @cookbook_loader = cookbook_loader end def build_provider(resource) provider_klass = resource.provider provider_klass ||= Chef::Platform.find_provider_for_node(@node, resource) Chef::Log.debug("#{resource} using #{provider_klass.to_s}") - provider = provider_klass.new(@node, resource) + provider = provider_klass.new(@node, resource, @collection, @definitions, @cookbook_loader) provider.load_current_resource provider end - + + def run_action(resource, ra) + provider = build_provider(resource) + provider.send("action_#{ra}") + + if resource.updated + resource.actions.each_key do |action| + if resource.actions[action].has_key?(:immediate) + resource.actions[action][:immediate].each do |r| + Chef::Log.info("#{resource} sending #{action} action to #{r} (immediate)") + run_action(r, action) + end + end + if resource.actions[action].has_key?(:delayed) + resource.actions[action][:delayed].each do |r| + @delayed_actions[r] = Hash.new unless @delayed_actions.has_key?(r) + @delayed_actions[r][action] = Array.new unless @delayed_actions[r].has_key?(action) + @delayed_actions[r][action] << lambda { + Chef::Log.info("#{resource} sending #{action} action to #{r} (delayed)") + } + end + end + end + end + end + def converge - delayed_actions = Hash.new + @delayed_actions = Hash.new - @collection.each do |resource| + @collection.execute_each_resource do |resource| begin Chef::Log.debug("Processing #{resource}") @@ -81,27 +108,7 @@ class Chef # Walk the actions for this resource, building the provider and running each. action_list = resource.action.kind_of?(Array) ? resource.action : [ resource.action ] action_list.each do |ra| - provider = build_provider(resource) - provider.send("action_#{ra}") - if resource.updated - resource.actions.each_key do |action| - if resource.actions[action].has_key?(:immediate) - resource.actions[action][:immediate].each do |r| - Chef::Log.info("#{resource} sending #{action} action to #{r} (immediate)") - build_provider(r).send("action_#{action}") - end - end - if resource.actions[action].has_key?(:delayed) - resource.actions[action][:delayed].each do |r| - delayed_actions[r] = Hash.new unless delayed_actions.has_key?(r) - delayed_actions[r][action] = Array.new unless delayed_actions[r].has_key?(action) - delayed_actions[r][action] << lambda { - Chef::Log.info("#{resource} sending #{action} action to #{r} (delayed)") - } - end - end - end - end + run_action(resource, ra) end rescue => e Chef::Log.error("#{resource} (#{resource.source_line}) had an error:\n#{e}\n#{e.backtrace}") @@ -110,10 +117,10 @@ class Chef end # Run all our :delayed actions - delayed_actions.each do |resource, action_hash| + @delayed_actions.each do |resource, action_hash| action_hash.each do |action, log_array| log_array.each { |l| l.call } # Call each log message - build_provider(resource).send("action_#{action}") + run_action(resource, action) end end diff --git a/chef/spec/data/lwrp/providers/buck_passer.rb b/chef/spec/data/lwrp/providers/buck_passer.rb new file mode 100644 index 0000000000..8d5156af81 --- /dev/null +++ b/chef/spec/data/lwrp/providers/buck_passer.rb @@ -0,0 +1,10 @@ +action :pass_buck do + lwrp_foo :prepared_thumbs do + action :prepare_thumbs + provider :lwrp_thumb_twiddler + end + lwrp_foo :twiddled_thumbs do + action :twiddle_thumbs + provider :lwrp_thumb_twiddler + end +end diff --git a/chef/spec/data/lwrp/providers/buck_passer_2.rb b/chef/spec/data/lwrp/providers/buck_passer_2.rb new file mode 100644 index 0000000000..d34da3c378 --- /dev/null +++ b/chef/spec/data/lwrp/providers/buck_passer_2.rb @@ -0,0 +1,10 @@ +action :pass_buck do + lwrp_bar :prepared_eyes do + action :prepare_eyes + provider :lwrp_paint_drying_watcher + end + lwrp_bar :dried_paint_watched do + action :watch_paint_dry + provider :lwrp_paint_drying_watcher + end +end diff --git a/chef/spec/data/lwrp/providers/embedded_resource_accesses_providers_scope.rb b/chef/spec/data/lwrp/providers/embedded_resource_accesses_providers_scope.rb new file mode 100644 index 0000000000..bebbb9664c --- /dev/null +++ b/chef/spec/data/lwrp/providers/embedded_resource_accesses_providers_scope.rb @@ -0,0 +1,14 @@ +# This action tests that embedded Resources have access to the enclosing Provider's +# lexical scope (as demonstrated by the call to new_resource) and that all parameters +# are passed properly (as demonstrated by the call to generate_new_name). +action :twiddle_thumbs do + lwrp_foo :foo do + monkey generate_new_name(new_resource.monkey){ 'the monkey' } + action :twiddle_thumbs + provider :lwrp_monkey_name_printer + end +end + +def generate_new_name(str, &block) + "#{str}, #{block.call}" +end diff --git a/chef/spec/data/lwrp/providers/monkey_name_printer.rb b/chef/spec/data/lwrp/providers/monkey_name_printer.rb new file mode 100644 index 0000000000..2d584d9ab4 --- /dev/null +++ b/chef/spec/data/lwrp/providers/monkey_name_printer.rb @@ -0,0 +1,3 @@ +action :twiddle_thumbs do + puts "my monkey's name is '#{new_resource.monkey}'" +end diff --git a/chef/spec/data/lwrp/providers/paint_drying_watcher.rb b/chef/spec/data/lwrp/providers/paint_drying_watcher.rb new file mode 100644 index 0000000000..04b4732dcc --- /dev/null +++ b/chef/spec/data/lwrp/providers/paint_drying_watcher.rb @@ -0,0 +1,7 @@ +action :prepare_eyes do + "Prepared" +end + +action :watch_paint_dry do + "This isn't very interesting" +end diff --git a/chef/spec/data/lwrp/providers/thumb_twiddler.rb b/chef/spec/data/lwrp/providers/thumb_twiddler.rb new file mode 100644 index 0000000000..7f014615db --- /dev/null +++ b/chef/spec/data/lwrp/providers/thumb_twiddler.rb @@ -0,0 +1,7 @@ +action :prepare_thumbs do + "Prepared" +end + +action :twiddle_thumbs do + "Twiddle twiddle" +end diff --git a/chef/spec/data/lwrp/resources/bar.rb b/chef/spec/data/lwrp/resources/bar.rb new file mode 100644 index 0000000000..bded6eeac3 --- /dev/null +++ b/chef/spec/data/lwrp/resources/bar.rb @@ -0,0 +1 @@ +actions :pass_buck, :prepare_eyes, :watch_paint_dry diff --git a/chef/spec/data/lwrp/resources/foo.rb b/chef/spec/data/lwrp/resources/foo.rb new file mode 100644 index 0000000000..ed65371997 --- /dev/null +++ b/chef/spec/data/lwrp/resources/foo.rb @@ -0,0 +1,3 @@ +actions :pass_buck, :prepare_thumbs, :twiddle_thumbs + +attribute :monkey, :kind_of => String diff --git a/chef/spec/lib/chef/provider/snakeoil.rb b/chef/spec/lib/chef/provider/snakeoil.rb index 5db174bdf7..23b8f21830 100644 --- a/chef/spec/lib/chef/provider/snakeoil.rb +++ b/chef/spec/lib/chef/provider/snakeoil.rb @@ -23,6 +23,11 @@ class Chef true end + def action_purr + @new_resource.updated = true + true + end + def action_sell true end diff --git a/chef/spec/unit/client_spec.rb b/chef/spec/unit/client_spec.rb index eb53803cf9..7520c53e1c 100644 --- a/chef/spec/unit/client_spec.rb +++ b/chef/spec/unit/client_spec.rb @@ -64,7 +64,37 @@ describe Chef::Client, "run" do @client.run end - it "should save the nodes state on the server (thrice!)" do + it "should synchronize definitions from the server" do + @client.should_receive(:sync_definitions).and_return(true) + @client.run + end + + it "should synchronize recipes from the server" do + @client.should_receive(:sync_recipes).and_return(true) + @client.run + end + + it "should synchronize and load library files from the server" do + @client.should_receive(:sync_library_files).and_return(true) + @client.run + end + + it "should synchronize and load attribute files from the server" do + @client.should_receive(:sync_attribute_files).and_return(true) + @client.run + end + + it "should synchronize providers from the server" do + @client.should_receive(:sync_provider_files).and_return(true) + @client.run + end + + it "should synchronize resources from the server" do + @client.should_receive(:sync_resource_files).and_return(true) + @client.run + end + + it "should save the nodes state on the server (twice!)" do @client.should_receive(:save_node).exactly(3).times.and_return(true) @client.run end @@ -79,7 +109,9 @@ end describe Chef::Client, "run_solo" do before(:each) do @client = Chef::Client.new - @client.stub!(:build_node).and_return(true) + [:run_ohai, :safe_name, :node_name, :build_node].each do |method| + @client.stub!(method).and_return(true) + end Chef::Compile.stub!(:new).and_return(mock("Chef::Compile", :null_object => true)) Chef::Runner.stub!(:new).and_return(mock("Chef::Runner", :null_object => true)) end diff --git a/chef/spec/unit/compile_spec.rb b/chef/spec/unit/compile_spec.rb index beda2a0e95..79ca3c5d06 100644 --- a/chef/spec/unit/compile_spec.rb +++ b/chef/spec/unit/compile_spec.rb @@ -22,7 +22,15 @@ 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")) - @compile = Chef::Compile.new + node = Chef::Node.new + node.stub!(:determine_node_name).and_return(true) + node.stub!(:load_libraries).and_return(true) + node.stub!(:load_providers).and_return(true) + node.stub!(:load_resources).and_return(true) + node.stub!(:load_attributes).and_return(true) + node.stub!(:load_definitions).and_return(true) + node.stub!(:load_recipes).and_return(true) + @compile = Chef::Compile.new(node) end it "should create a new Chef::Compile" do diff --git a/chef/spec/unit/lwrp_spec.rb b/chef/spec/unit/lwrp_spec.rb new file mode 100644 index 0000000000..a993b8446c --- /dev/null +++ b/chef/spec/unit/lwrp_spec.rb @@ -0,0 +1,143 @@ +# +# Author:: Christopher Walters (<cw@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 File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper")) + +Dir[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| + Chef::Provider.build_from_file("lwrp", file) +end + +describe Chef::Resource do + + it "should load the resource into a properly-named class" do + Chef::Resource.const_get("LwrpFoo").should be_kind_of(Class) + end + + it "should set resource_name" do + Chef::Resource::LwrpFoo.new("blah").resource_name.should eql(:lwrp_foo) + end + + it "should add the specified actions to the allowed_actions array" do + Chef::Resource::LwrpFoo.new("blah").allowed_actions.should include(:pass_buck, :twiddle_thumbs) + end + + it "should create a method for each attribute" do + Chef::Resource::LwrpFoo.new("blah").methods.should include("monkey") + end + + it "should build attribute methods that respect validation rules" do + lambda { Chef::Resource::LwrpFoo.new("blah").monkey(42) }.should raise_error(ArgumentError) + end + +end + +describe Chef::Provider do + + it "should load the provider into a properly-named class" do + Chef::Provider.const_get("LwrpBuckPasser").should be_kind_of(Class) + end + + 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") + end + + it "should insert resources embedded in the provider into the middle of the resource collection" do + node = Chef::Node.new + rc = Chef::ResourceCollection.new + + injector = Chef::Resource::LwrpFoo.new("morpheus") + injector.action(:pass_buck) + injector.provider(:lwrp_buck_passer) + dummy = Chef::Resource::ZenMaster.new("keanu reeves") + dummy.provider(Chef::Provider::Easy) + rc.insert(injector) + rc.insert(dummy) + + Chef::Runner.new(node, rc).converge + + rc[0].should eql(injector) + rc[1].name.should eql(:prepared_thumbs) + rc[2].name.should eql(:twiddled_thumbs) + rc[3].should eql(dummy) + end + + it "should insert embedded resources from multiple providers, including from the last position, properly into the resource collection" do + node = Chef::Node.new + rc = Chef::ResourceCollection.new + + injector = Chef::Resource::LwrpFoo.new("morpheus") + injector.action(:pass_buck) + injector.provider(:lwrp_buck_passer) + injector2 = Chef::Resource::LwrpBar.new("tank") + injector2.action(:pass_buck) + injector2.provider(:lwrp_buck_passer_2) + dummy = Chef::Resource::ZenMaster.new("keanu reeves") + dummy.provider(Chef::Provider::Easy) + + rc.insert(injector) + rc.insert(dummy) + rc.insert(injector2) + + Chef::Runner.new(node, rc).converge + + rc[0].should eql(injector) + rc[1].name.should eql(:prepared_thumbs) + rc[2].name.should eql(:twiddled_thumbs) + rc[3].should eql(dummy) + rc[4].should eql(injector2) + rc[5].name.should eql(:prepared_eyes) + rc[6].name.should eql(:dried_paint_watched) + end + + it "should properly handle a new_resource reference" do + node = Chef::Node.new + rc = Chef::ResourceCollection.new + + res = Chef::Resource::LwrpFoo.new("morpheus") + res.monkey("bob") + res.action(:twiddle_thumbs) + res.provider(:lwrp_monkey_name_printer) + rc.insert(res) + + STDOUT.should_receive(:write).with("my monkey's name is 'bob'").exactly(:once) + STDOUT.should_receive(:write).with("\n").exactly(:once) + Chef::Runner.new(node, rc).converge + end + + it "should properly handle an embedded Resource accessing the enclosing Provider's scope" do + node = Chef::Node.new + rc = Chef::ResourceCollection.new + + res = Chef::Resource::LwrpFoo.new("morpheus") + res.monkey("bob") + res.action(:twiddle_thumbs) + res.provider(:lwrp_embedded_resource_accesses_providers_scope) + rc.insert(res) + + STDOUT.should_receive(:write).with("my monkey's name is 'bob, the monkey'").exactly(:once) + STDOUT.should_receive(:write).with("\n").exactly(:once) + Chef::Runner.new(node, rc).converge + end + +end diff --git a/chef/spec/unit/mixin/command_spec.rb b/chef/spec/unit/mixin/command_spec.rb index 816e08f8a6..97d4eb5251 100644 --- a/chef/spec/unit/mixin/command_spec.rb +++ b/chef/spec/unit/mixin/command_spec.rb @@ -29,6 +29,18 @@ describe Chef::Mixin::Command, "popen4" do end end + it "should default all commands to be run in the POSIX standard C locale" do + popen4("echo $LC_ALL") do |pid, stdin, stdout, stderr| + stdout.read.strip.should == "C" + end + end + + it "should respect locale when specified explicitly" do + popen4("echo $LC_ALL", :environment => {"LC_ALL" => "es"}) do |pid, stdin, stdout, stderr| + stdout.read.strip.should == "es" + end + end + end describe Chef::Mixin::Command, "run_command" do @@ -45,4 +57,4 @@ describe Chef::Mixin::Command, "run_command" do end end -end
\ No newline at end of file +end diff --git a/chef/spec/unit/mixin/find_preferred_file_spec.rb b/chef/spec/unit/mixin/find_preferred_file_spec.rb new file mode 100644 index 0000000000..70448ef50d --- /dev/null +++ b/chef/spec/unit/mixin/find_preferred_file_spec.rb @@ -0,0 +1,105 @@ +# +# Author:: Daniel DeLeo (<dan@kallistec.com>) +# Copyright:: Copyright (c) 2009 Daniel DeLeo +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper")) + +class PreferredFileTestHarness + include Chef::Mixin::FindPreferredFile +end + + +describe Chef::Mixin::FindPreferredFile do + + before do + @finder = PreferredFileTestHarness.new + + @default_file_list = %q{ +/srv/chef/cookbooks/apache2/templates/default/mods/status.conf.erb +/srv/chef/cookbooks/apache2/templates/default/mods/fcgid.conf.erb +/srv/chef/cookbooks/apache2/templates/default/mods/alias.conf.erb +/srv/chef/cookbooks/apache2/templates/default/a2dismod.erb +/srv/chef/cookbooks/apache2/templates/default/mods/ssl.conf.erb +/srv/chef/cookbooks/apache2/templates/default/default-site.erb +/srv/chef/cookbooks/apache2/templates/default/web_app.conf.erb +/srv/chef/cookbooks/apache2/templates/default/mods/dir.conf.erb +/srv/chef/cookbooks/apache2/templates/default/port_apache.erb +/srv/chef/cookbooks/apache2/templates/default/charset.erb +/srv/chef/cookbooks/apache2/templates/default/moin.erb +/srv/chef/cookbooks/apache2/templates/default/mods/negotiation.conf.erb +/srv/chef/cookbooks/apache2/templates/default/mods/autoindex.conf.erb +/srv/chef/cookbooks/apache2/templates/default/mods/proxy.conf.erb +/srv/chef/cookbooks/apache2/templates/default/a2dissite.erb +/srv/chef/cookbooks/apache2/templates/default/mods/deflate.conf.erb +/srv/chef/cookbooks/apache2/templates/default/mods/setenvif.conf.erb +/srv/chef/cookbooks/apache2/templates/default/apache2.conf.erb +/srv/chef/cookbooks/apache2/templates/default/a2enmod.erb +/srv/chef/cookbooks/apache2/templates/default/mods/mime.conf.erb +/srv/chef/cookbooks/apache2/templates/default/security.erb +/srv/chef/cookbooks/apache2/templates/default/ports.conf.erb +/srv/chef/cookbooks/apache2/templates/default/a2ensite.erb}.strip.split("\n") + end + + def default_file_hash + hsh = {} + @default_file_list.each do |filename| + hsh[filename] = filename + end + hsh + end + + describe "finding preferred files from the list" do + + it "finds the default file out of a list when nothing else matches" do + @finder.stub!(:load_cookbook_files).and_return(default_file_hash) + args = %w{no_cookbook_id no_filetype mods/deflate.conf.erb nohost.example.com noplatform noversion} + @finder.find_preferred_file(*args).should == "/srv/chef/cookbooks/apache2/templates/default/mods/deflate.conf.erb" + end + + it "prefers a platform specific file to the default" do + @default_file_list << "/srv/chef/cookbooks/apache2/templates/ubuntu/mods/deflate.conf.erb" + @finder.stub!(:load_cookbook_files).and_return(default_file_hash) + args = %w{no_cookbook_id no_filetype mods/deflate.conf.erb nohost.example.com ubuntu noversion} + @finder.find_preferred_file(*args).should == "/srv/chef/cookbooks/apache2/templates/ubuntu/mods/deflate.conf.erb" + end + + it "prefers a platform + version specific file to the default or platform specific version" do + @default_file_list << "/srv/chef/cookbooks/apache2/templates/ubuntu/mods/deflate.conf.erb" + @default_file_list << "/srv/chef/cookbooks/apache2/templates/ubuntu-8.04/mods/deflate.conf.erb" + @finder.stub!(:load_cookbook_files).and_return(default_file_hash) + args = %w{no_cookbook_id no_filetype mods/deflate.conf.erb nohost.example.com ubuntu 8.04} + @finder.find_preferred_file(*args).should == "/srv/chef/cookbooks/apache2/templates/ubuntu-8.04/mods/deflate.conf.erb" + end + + it "prefers a host specific file to any other" do + @default_file_list << "/srv/chef/cookbooks/apache2/templates/ubuntu/mods/deflate.conf.erb" + @default_file_list << "/srv/chef/cookbooks/apache2/templates/ubuntu-8.04/mods/deflate.conf.erb" + @default_file_list << "/srv/chef/cookbooks/apache2/templates/host-foo.example.com/mods/deflate.conf.erb" + @finder.stub!(:load_cookbook_files).and_return(default_file_hash) + args = %w{no_cookbook_id no_filetype mods/deflate.conf.erb foo.example.com ubuntu 8.04} + @finder.find_preferred_file(*args).should == "/srv/chef/cookbooks/apache2/templates/host-foo.example.com/mods/deflate.conf.erb" + end + + it "raises an error when no file can be found" do + @finder.stub!(:load_cookbook_files).and_return(default_file_hash) + args = %w{no_cookbook_id no_filetype mods/me_no_findy.erb nohost.example.com noplatform noversion} + lambda {@finder.find_preferred_file(*args)}.should raise_error Chef::Exceptions::FileNotFound + end + + end + +end
\ No newline at end of file diff --git a/chef/spec/unit/provider/cron_spec.rb b/chef/spec/unit/provider/cron_spec.rb index eda942e14b..fc73825299 100644 --- a/chef/spec/unit/provider/cron_spec.rb +++ b/chef/spec/unit/provider/cron_spec.rb @@ -36,6 +36,7 @@ describe Chef::Provider::Cron, "load_current_resource" do @node = mock("Chef::Node", :null_object => true) @new_resource = mock("Chef::Resource::Cron", :null_object => true, + :user => "root", :name => "foo", :minute => "30", :command => "/bin/true" @@ -69,8 +70,8 @@ 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"). - and_yield("* 5 * * * /bin/true") + @stdout.stub!(:each).and_yield("# Chef Name: foo\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 @@ -78,6 +79,57 @@ describe Chef::Provider::Cron, "load_current_resource" do end +describe Chef::Provider::Cron, "compare_cron" do + before(:each) do + @node = mock("Chef::Node", :null_object => true) + @new_resource = mock("Chef::Resource::Cron", + :null_object => true, + :user => "root", + :name => "foo", + :minute => "30", + :hour => "2", + :day => "30", + :month => "5", + :weekday => "3", + :command => "/bin/true", + :mailto => "test@example.com", + :path => "/usr/bin:/bin", + :shell => "/bin/zsh", + :home => "/home/thom" + ) + @current_resource = mock("Chef::Resource::Cron", + :null_object => true, + :user => "root", + :name => "foo", + :minute => "30", + :hour => "2", + :day => "30", + :month => "5", + :weekday => "3", + :command => "/bin/true", + :mailto => "test@example.com", + :path => "/usr/bin:/bin", + :shell => "/bin/zsh", + :home => "/home/thom" + ) + @provider = Chef::Provider::Cron.new(@node, @new_resource) + @provider.current_resource = @current_resource + end + + %w{ minute hour day month weekday command mailto path shell home }.each do |attribute| + it "should return true if #{attribute} doesn't match" do + @new_resource.should_receive(attribute).exactly(2).times.and_return(true) + @current_resource.should_receive(attribute).once.and_return(false) + @provider.compare_cron.should eql(true) + end + end + + it "should return false if the objects are identical" do + @provider.compare_cron.should eql(false) + end +end + + describe Chef::Provider::Cron, "action_create" do before(:each) do @node = mock("Chef::Node", :null_object => true) @@ -85,16 +137,31 @@ describe Chef::Provider::Cron, "action_create" do :null_object => true, :name => "foo", :minute => "30", + :hour => "*", + :day => "*", + :month => "*", + :weekday => "*", + :mailto => nil, + :path => nil, + :shell => nil, + :home => nil, :command => "/bin/true" ) @current_resource = mock("Chef::Resource::Cron", :null_object => true, :name => "foo", - :minute => "30", + :minute => "*", + :hour => "5", + :day => "*", + :month => "*", + :weekday => "*", + :mailto => nil, + :path => nil, + :shell => nil, + :home => nil, :command => "/bin/true" ) @provider = Chef::Provider::Cron.new(@node, @new_resource) - end it "should add the cron entry if cron exists" do @@ -103,10 +170,8 @@ describe Chef::Provider::Cron, "action_create" 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: bar"). - and_yield("* 10 * * * /bin/false"). - and_yield("# Chef Name: foo"). - and_yield("* 5 * * * /bin/true") + @stdout.stub!(:each_line).and_yield("# Chef Name: bar\n"). + and_yield("* 10 * * * /bin/false\n") @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) Chef::Log.should_receive(:info).with("Added cron '#{@new_resource.name}'") @provider.action_create @@ -118,10 +183,10 @@ describe Chef::Provider::Cron, "action_create" 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: bar"). - and_yield("* 10 * * * /bin/false"). - and_yield("# Chef Name: foo"). - and_yield("* 5 * * * /bin/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("* 5 * * * /bin/true\n") @provider.cron_empty=true @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) Chef::Log.should_receive(:info).with("Added cron '#{@new_resource.name}'") @@ -129,22 +194,61 @@ describe Chef::Provider::Cron, "action_create" do end it "should update the cron entry if it exists and has changed" do + @provider.current_resource = @current_resource @status = mock("Status", :exitstatus => 0) @stdin = mock("STDIN", :null_object => true) @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: bar"). - and_yield("* 10 * * * /bin/false"). - and_yield("# Chef Name: foo"). - and_yield("* 5 * * * /bin/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("* 5 * * * /bin/true\n") @provider.cron_exists=true @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) Chef::Log.should_receive(:info).with("Updated cron '#{@new_resource.name}'") + @provider.should_receive(:compare_cron).once.and_return(true) @provider.action_create end it "should not update the cron entry if it exists and has not changed" do + @status = mock("Status", :exitstatus => 0) + @stdin = mock("STDIN", :null_object => true) + @stdout = mock("STDOUT", :null_object => true) + @stderr = mock("STDERR", :null_object => true) + @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("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}'") + Chef::Log.should_receive(:debug).with("Skipping existing cron entry '#{@new_resource.name}'") + @provider.should_receive(:compare_cron).once.and_return(false) + @provider.cron_exists = true + @provider.action_create + end + + it "should update the cron entry if it exists and has changed environment variables" do + @provider.current_resource = @current_resource + @status = mock("Status", :exitstatus => 0) + @stdin = mock("STDIN", :null_object => true) + @stdout = mock("STDOUT", :null_object => true) + @stderr = mock("STDERR", :null_object => true) + @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("MAILTO=warn@example.com\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}'") + @provider.cron_exists = true + @provider.should_receive(:compare_cron).once.and_return(true) + @provider.action_create + end + + 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", @@ -153,26 +257,31 @@ describe Chef::Provider::Cron, "action_create" do :day => "*", :month => "*", :weekday => "*", + :mailto => "test@example.com", + :path => nil, + :shell => nil, + :home => nil, :command => "/bin/true" ) provider = Chef::Provider::Cron.new(@node, resource) - + provider.current_resource = @current_resource @status = mock("Status", :exitstatus => 0) @stdin = mock("STDIN", :null_object => true) @stdout = mock("STDOUT", :null_object => true) @stderr = mock("STDERR", :null_object => true) @pid = mock("PID", :null_object => true) - @stdout.stub!(:each_line).and_yield("# Chef Name: bar"). - and_yield("* 10 * * * /bin/false"). + @stdout.stub!(:each_line).and_yield("# Chef Name: bar\n"). + and_yield("* 10 * * * /bin/false\n"). and_yield("# Chef Name: foo\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}'") - Chef::Log.should_receive(:debug).with("Skipping existing cron entry '#{@new_resource.name}'") + Chef::Log.should_receive(:info).with("Updated cron '#{@new_resource.name}'") provider.cron_exists = true + provider.should_receive(:compare_cron).once.and_return(true) provider.action_create end + end describe Chef::Provider::Cron, "action_delete" do @@ -200,10 +309,10 @@ describe Chef::Provider::Cron, "action_delete" 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: bar"). - and_yield("* 10 * * * /bin/false"). - and_yield("# Chef Name: foo"). - and_yield("* 30 * * * /bin/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("* 30 * * * /bin/true\n") @provider.cron_exists=true @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) Chef::Log.should_receive(:debug).with("Deleted cron '#{@new_resource.name}'") @@ -217,7 +326,7 @@ describe Chef::Provider::Cron, "action_delete" 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: bar"). + @stdout.stub!(:each_line).and_yield("# Chef Name: bar"). and_yield("* 10 * * * /bin/false") @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) Chef::Log.should_not_receive(:debug).with("Deleted cron '#{@new_resource.name}'") diff --git a/chef/spec/unit/provider/deploy/revision_spec.rb b/chef/spec/unit/provider/deploy/revision_spec.rb new file mode 100644 index 0000000000..a6602cb2d1 --- /dev/null +++ b/chef/spec/unit/provider/deploy/revision_spec.rb @@ -0,0 +1,72 @@ +# +# Author:: Daniel DeLeo (<dan@kallistec.com>) +# Copyright:: Copyright (c) 2008 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require File.expand_path(File.join(File.dirname(__FILE__),"..", "..", "..", "spec_helper")) + +describe Chef::Provider::Deploy::Revision do + + before do + Chef::Config[:file_cache_path] = '/tmp/foo' + @resource = Chef::Resource::Deploy.new("/my/deploy/dir") + @resource.revision("8a3195bf3efa246f743c5dfa83683201880f935c") + @node = Chef::Node.new + @provider = Chef::Provider::Deploy::Revision.new(@node, @resource) + @provider.load_current_resource + @runner = mock("runnah", :null_object => true) + Chef::Runner.stub!(:new).and_return(@runner) + @expected_release_dir = "/my/deploy/dir/releases/8a3195bf3efa246f743c5dfa83683201880f935c" + end + + after do + # Make sure we don't keep any state in our tests + FileUtils.rm_rf Chef::FileCache.create_cache_path('revision-deploys',false) + end + + + it "uses the resolved revision from the SCM as the release slug" do + @provider.scm_provider.stub!(:revision_slug).and_return("uglySlugly") + @provider.send(:release_slug).should == "uglySlugly" + end + + it "deploys to a dir named after the revision" do + @provider.release_path.should == @expected_release_dir + end + + it "stores the release dir in the file cache when copying the cached repo" do + FileUtils.stub!(:mkdir_p) + FileUtils.stub!(:cp_r) + @provider.copy_cached_repo + @provider.stub!(:release_slug).and_return("73219b87e977d9c7ba1aa57e9ad1d88fa91a0ec2") + @provider.load_current_resource + @provider.copy_cached_repo + second_release = "/my/deploy/dir/releases/73219b87e977d9c7ba1aa57e9ad1d88fa91a0ec2" + @provider.all_releases.should == [@expected_release_dir,second_release] + end + + it "removes a release from the file cache when it's deleted by :cleanup!" do + %w{first second third fourth fifth latest}.each do |release_name| + @provider.send(:release_created, release_name) + end + @provider.all_releases.should == %w{first second third fourth fifth latest} + + FileUtils.stub(:rm_rf) + @provider.cleanup! + @provider.all_releases.should == %w{second third fourth fifth latest} + end + +end
\ No newline at end of file diff --git a/chef/spec/unit/provider/deploy/timestamped_spec.rb b/chef/spec/unit/provider/deploy/timestamped_spec.rb new file mode 100644 index 0000000000..984c413576 --- /dev/null +++ b/chef/spec/unit/provider/deploy/timestamped_spec.rb @@ -0,0 +1,38 @@ +# +# Author:: Daniel DeLeo (<dan@kallistec.com>) +# Copyright:: Copyright (c) 2008 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require File.expand_path(File.join(File.dirname(__FILE__),"..", "..", "..", "spec_helper")) + +describe Chef::Provider::Deploy::Timestamped do + + before do + @release_time = Time.utc( 2004, 8, 15, 16, 23, 42) + Time.stub!(:now).and_return(@release_time) + @expected_release_dir = "/my/deploy/dir/releases/20040815162342" + @resource = Chef::Resource::Deploy.new("/my/deploy/dir") + @node = Chef::Node.new + @timestamped_deploy = Chef::Provider::Deploy::Timestamped.new(@node, @resource) + @runner = mock("runnah", :null_object => true) + Chef::Runner.stub!(:new).and_return(@runner) + end + + it "gives a timestamp for release_slug" do + @timestamped_deploy.send(:release_slug).should == "20040815162342" + 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 new file mode 100644 index 0000000000..8993f07b92 --- /dev/null +++ b/chef/spec/unit/provider/deploy_spec.rb @@ -0,0 +1,387 @@ +# +# Author:: Daniel DeLeo (<dan@kallistec.com>) +# Copyright:: Copyright (c) 2008 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper")) + +describe Chef::Provider::Deploy do + + before do + @release_time = Time.utc( 2004, 8, 15, 16, 23, 42) + Time.stub!(:now).and_return(@release_time) + @expected_release_dir = "/my/deploy/dir/releases/20040815162342" + @resource = Chef::Resource::Deploy.new("/my/deploy/dir") + @node = Chef::Node.new + @provider = Chef::Provider::Deploy.new(@node, @resource) + @provider.stub!(:release_slug) + @provider.stub!(:release_path).and_return(@expected_release_dir) + @runner = mock("runnah", :null_object => true) + Chef::Runner.stub!(:new).and_return(@runner) + end + + it "supports :deploy and :rollback actions" do + @provider.should respond_to(:action_deploy) + @provider.should respond_to(:action_rollback) + end + + it "updates and copies the repo, then does a migrate, symlink, restart, restart, cleanup on deploy" do + @provider.should_receive(:enforce_ownership).twice + @provider.should_receive(:update_cached_repo) + @provider.should_receive(:copy_cached_repo) + @provider.should_receive(:install_gems) + @provider.should_receive(:callback).with(:before_migrate, nil) + @provider.should_receive(:migrate) + @provider.should_receive(:callback).with(:before_symlink, nil) + @provider.should_receive(:symlink) + @provider.should_receive(:callback).with(:before_restart, nil) + @provider.should_receive(:restart) + @provider.should_receive(:callback).with(:after_restart, nil) + @provider.should_receive(:cleanup!) + @provider.deploy + end + + it "does not deploy when using the :deploy action if there is already a deploy at release_path" do + @provider.stub!(:all_releases).and_return([@expected_release_dir]) + @provider.should_not_receive(:enforce_ownership) + @provider.should_not_receive(:update_cached_repo) + @provider.action_deploy + end + + it "calls deploy when deploying a new release" do + @provider.stub!(:all_releases).and_return([]) + @provider.should_receive(:deploy) + @provider.action_deploy + end + + it "Removes the old release before deploying when force deploying over it" do + @provider.stub!(:all_releases).and_return([@expected_release_dir]) + FileUtils.should_receive(:rm_rf).with(@expected_release_dir) + @provider.should_receive(:deploy) + @provider.action_force_deploy + end + + it "deploys as normal when force deploying and there's no prior release at the same path" do + @provider.stub!(:all_releases).and_return([]) + @provider.should_receive(:deploy) + @provider.action_force_deploy + end + + it "sets the release path to the penultimate release, symlinks, and rm's the last release on rollback" do + @provider.unstub!(:release_path) + all_releases = ["/my/deploy/dir/releases/20040815162342", "/my/deploy/dir/releases/20040700000000", + "/my/deploy/dir/releases/20040600000000", "/my/deploy/dir/releases/20040500000000"].sort! + Dir.stub!(:glob).with("/my/deploy/dir/releases/*").and_return(all_releases) + @provider.should_receive(:symlink) + FileUtils.should_receive(:rm_rf).with("/my/deploy/dir/releases/20040815162342") + @provider.action_rollback + @provider.release_path.should eql("/my/deploy/dir/releases/20040700000000") + end + + it "raises a runtime error when there's no release to rollback to" do + all_releases = [] + Dir.stub!(:glob).with("/my/deploy/dir/releases/*").and_return(all_releases) + lambda {@provider.action_rollback}.should raise_error(RuntimeError) + end + + it "runs the new resource collection in the runner during a callback" do + @runner.should_receive(:converge) + callback_code = lambda { :noop } + @provider.callback(:whatevs, callback_code) + end + + it "loads callback files from the release/ dir if the file exists" do + foo_callback = @expected_release_dir + "/deploy/foo.rb" + ::File.should_receive(:exist?).with(foo_callback).twice.and_return(true) + ::Dir.should_receive(:chdir).with(@expected_release_dir).and_yield + @provider.should_receive(:from_file).with(foo_callback) + @provider.callback(:foo, "deploy/foo.rb") + end + + it "raises a runtime error if a callback file is explicitly specified but does not exist" do + baz_callback = @expected_release_dir + "/deploy/baz.rb" + ::File.should_receive(:exist?).with(baz_callback).and_return(false) + lambda {@provider.callback(:foo, "deploy/baz.rb")}.should raise_error(RuntimeError) + end + + it "runs a default callback if the callback code is nil" do + bar_callback = @expected_release_dir + "/deploy/bar.rb" + ::File.should_receive(:exist?).with(bar_callback).and_return(true) + ::Dir.should_receive(:chdir).with(@expected_release_dir).and_yield + @provider.should_receive(:from_file).with(bar_callback) + @provider.callback(:bar, nil) + end + + it "skips an eval callback if the file doesn't exist" do + barbaz_callback = @expected_release_dir + "/deploy/barbaz.rb" + ::File.should_receive(:exist?).with(barbaz_callback).and_return(false) + @provider.should_not_receive(:from_file) + @provider.callback(:barbaz, nil) + end + + it "gets a SCM provider as specified by its resource" do + @provider.scm_provider.should be_an_instance_of(Chef::Provider::Git) + @provider.scm_provider.new_resource.destination.should eql("/my/deploy/dir/shared/cached-copy/") + end + + it "syncs the cached copy of the repo" do + @provider.scm_provider.should_receive(:action_sync) + @provider.update_cached_repo + end + + it "makes a copy of the cached repo in releases dir" do + FileUtils.should_receive(:mkdir_p).with("/my/deploy/dir/releases") + FileUtils.should_receive(:cp_r).with( "/my/deploy/dir/shared/cached-copy/.", + @expected_release_dir, + :preserve => true) + @provider.copy_cached_repo + end + + it "calls the internal callback :release_created when copying the cached repo" do + FileUtils.stub!(:mkdir_p) + FileUtils.stub!(:cp_r) + @provider.should_receive(:release_created) + @provider.copy_cached_repo + end + + it "chowns the whole release dir to user and group specified in the resource" do + @resource.user "foo" + @resource.group "bar" + FileUtils.should_receive(:chown_R).with("foo", "bar", "/my/deploy/dir") + @provider.enforce_ownership + end + + it "skips the migration when resource.migrate => false" do + @resource.migrate false + @provider.should_not_receive :run_command + @provider.migrate + end + + it "links the database.yml and runs resource.migration_command when resource.migrate #=> true" do + @resource.migrate true + @resource.migration_command "migration_foo" + @resource.user "deployNinja" + @resource.group "deployNinjas" + @resource.environment "RAILS_ENV" => "production" + FileUtils.should_receive(:ln_sf).with("/my/deploy/dir/shared/config/database.yml", @expected_release_dir + "/config/database.yml") + @provider.should_receive(:enforce_ownership) + @provider.should_receive(:run_command).with(:command => "migration_foo", :cwd => @expected_release_dir, + :user => "deployNinja", :group => "deployNinjas", + :environment => {"RAILS_ENV"=>"production"}) + @provider.migrate + end + + it "purges the current release's /log /tmp/pids/ and /public/system directories" do + FileUtils.should_receive(:rm_rf).with(@expected_release_dir + "/log") + FileUtils.should_receive(:rm_rf).with(@expected_release_dir + "/tmp/pids") + FileUtils.should_receive(:rm_rf).with(@expected_release_dir + "/public/system") + @provider.purge_tempfiles_from_current_release + end + + it "symlinks temporary files and logs from the shared dir into the current release" do + FileUtils.should_receive(:mkdir_p).with(@expected_release_dir + "/tmp") + FileUtils.should_receive(:mkdir_p).with(@expected_release_dir + "/public") + FileUtils.should_receive(:mkdir_p).with(@expected_release_dir + "/config") + FileUtils.should_receive(:ln_sf).with("/my/deploy/dir/shared/system", @expected_release_dir + "/public/system") + FileUtils.should_receive(:ln_sf).with("/my/deploy/dir/shared/pids", @expected_release_dir + "/tmp/pids") + FileUtils.should_receive(:ln_sf).with("/my/deploy/dir/shared/log", @expected_release_dir + "/log") + FileUtils.should_receive(:ln_sf).with("/my/deploy/dir/shared/config/database.yml", @expected_release_dir + "/config/database.yml") + @provider.should_receive(:enforce_ownership) + @provider.link_tempfiles_to_current_release + end + + it "symlinks the current release dir into production" do + FileUtils.should_receive(:rm_f).with("/my/deploy/dir/current") + FileUtils.should_receive(:ln_sf).with(@expected_release_dir, "/my/deploy/dir/current") + @provider.should_receive(:enforce_ownership) + @provider.link_current_release_to_production + end + + context "with a customized app layout" do + + before do + @resource.purge_before_symlink(%w{foo bar}) + @resource.create_dirs_before_symlink(%w{baz qux}) + @resource.symlinks "foo/bar" => "foo/bar", "baz" => "qux/baz" + @resource.symlink_before_migrate "radiohead/in_rainbows.yml" => "awesome" + end + + it "purges the purge_before_symlink directories" do + FileUtils.should_receive(:rm_rf).with(@expected_release_dir + "/foo") + FileUtils.should_receive(:rm_rf).with(@expected_release_dir + "/bar") + @provider.purge_tempfiles_from_current_release + end + + it "symlinks files from the shared directory to the current release directory" do + FileUtils.should_receive(:mkdir_p).with(@expected_release_dir + "/baz") + FileUtils.should_receive(:mkdir_p).with(@expected_release_dir + "/qux") + FileUtils.should_receive(:ln_sf).with("/my/deploy/dir/shared/foo/bar", @expected_release_dir + "/foo/bar") + FileUtils.should_receive(:ln_sf).with("/my/deploy/dir/shared/baz", @expected_release_dir + "/qux/baz") + FileUtils.should_receive(:ln_sf).with("/my/deploy/dir/shared/radiohead/in_rainbows.yml", @expected_release_dir + "/awesome") + @provider.should_receive(:enforce_ownership) + @provider.link_tempfiles_to_current_release + end + + end + + it "does nothing for restart if restart_command is empty" do + @provider.should_not_receive(:run_command) + @provider.restart + end + + it "runs the restart command in the current application dir when the resource has a restart_command" do + @resource.restart_command "restartcmd" + @provider.should_receive(:run_command).with(:command => "restartcmd", :cwd => "/my/deploy/dir/current") + @provider.restart + end + + it "lists all available releases" do + all_releases = ["/my/deploy/dir/20040815162342", "/my/deploy/dir/20040700000000", + "/my/deploy/dir/20040600000000", "/my/deploy/dir/20040500000000"].sort! + Dir.should_receive(:glob).with("/my/deploy/dir/releases/*").and_return(all_releases) + @provider.all_releases.should eql(all_releases) + end + + it "removes all but the 5 newest releases" do + all_releases = ["/my/deploy/dir/20040815162342", "/my/deploy/dir/20040700000000", + "/my/deploy/dir/20040600000000", "/my/deploy/dir/20040500000000", + "/my/deploy/dir/20040400000000", "/my/deploy/dir/20040300000000", + "/my/deploy/dir/20040200000000", "/my/deploy/dir/20040100000000"].sort! + @provider.stub!(:all_releases).and_return(all_releases) + FileUtils.should_receive(:rm_rf).with("/my/deploy/dir/20040100000000") + FileUtils.should_receive(:rm_rf).with("/my/deploy/dir/20040200000000") + FileUtils.should_receive(:rm_rf).with("/my/deploy/dir/20040300000000") + @provider.cleanup! + end + + it "fires a callback for :release_deleted when deleting an old release" do + all_releases = ["/my/deploy/dir/20040815162342", "/my/deploy/dir/20040700000000", + "/my/deploy/dir/20040600000000", "/my/deploy/dir/20040500000000", + "/my/deploy/dir/20040400000000", "/my/deploy/dir/20040300000000"].sort! + @provider.stub!(:all_releases).and_return(all_releases) + FileUtils.stub!(:rm_rf) + @provider.should_receive(:release_deleted).with("/my/deploy/dir/20040300000000") + @provider.cleanup! + end + + it "puts resource.to_hash in @configuration for backwards compat with capistano-esque deploy hooks" do + @provider.instance_variable_get(:@configuration).should == @resource.to_hash + end + + it "sets @configuration[:environment] to the value of RAILS_ENV for backwards compat reasons" do + resource = Chef::Resource::Deploy.new("/my/deploy/dir") + resource.environment "production" + provider = Chef::Provider::Deploy.new(@node, resource) + provider.instance_variable_get(:@configuration)[:environment].should eql("production") + end + + it "shouldn't give a no method error on migrate if the environment is nil" do + @provider.stub!(:enforce_ownership) + @provider.stub!(:link_shared_db_config_to_current_release) + @provider.stub!(:run_command) + @provider.migrate + end + + 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) + @provider.callback(:whateverz, recipe_code) + end + + it "loads a recipe file from the specified path and from_file evals it" do + ::File.should_receive(:exist?).with(@expected_release_dir + "/chefz/foobar_callback.rb").twice.and_return(true) + ::Dir.should_receive(:chdir).with(@expected_release_dir).and_yield + @provider.should_receive(:from_file).with(@expected_release_dir + "/chefz/foobar_callback.rb") + @provider.callback(:whateverz, "chefz/foobar_callback.rb") + end + + it "instance_evals a block/proc for restart command" do + snitch = nil + restart_cmd = lambda {snitch = 42} + @resource.restart(&restart_cmd) + @provider.restart + snitch.should == 42 + end + + end + + describe "API bridge to capistrano" do + it "defines sudo as a forwarder to execute" do + @provider.should_receive(:execute).with("the moon, fool") + @provider.sudo("the moon, fool") + end + + it "defines run as a forwarder to execute, setting the user to new_resource.user" do + mock_execution = mock("Resource::Execute") + @provider.should_receive(:execute).with("iGoToHell4this").and_return(mock_execution) + @resource.user("notCoolMan") + mock_execution.should_receive(:user).with("notCoolMan") + @provider.run("iGoToHell4this") + end + + it "converts sudo and run to exec resources in hooks" do + runner = mock("tehRunner", :null_object => true) + Chef::Runner.stub!(:new).and_return(runner) + + snitch = nil + @resource.user("tehCat") + + callback_code = lambda do + snitch = 42 + temp_collection = self.instance_variable_get(:@collection) + run("tehMice") + snitch = temp_collection.lookup("execute[tehMice]") + end + + @provider.callback(:phony, callback_code) + snitch.should be_an_instance_of(Chef::Resource::Execute) + snitch.user.should == "tehCat" + end + end + + describe "installing gems from a gems.yml" do + + before do + ::File.stub!(:exist?).with("#{@expected_release_dir}/gems.yml").and_return(true) + @gem_list = [{:name=>"ezmobius-nanite",:version=>"0.4.1.2"},{:name=>"eventmachine", :version=>"0.12.9"}] + end + + it "reads a gems.yml file, creating gem providers for each with action :upgrade" do + IO.should_receive(:read).with("#{@expected_release_dir}/gems.yml").and_return("cookie") + YAML.should_receive(:load).with("cookie").and_return(@gem_list) + + gems = @provider.send(:gem_packages) + + gems.map { |g| g.action }.should == [[:install], [:install]] + gems.map { |g| g.name }.should == %w{ezmobius-nanite eventmachine} + gems.map { |g| g.version }.should == %w{0.4.1.2 0.12.9} + end + + it "takes a list of gem providers converges them" do + IO.stub!(:read) + YAML.stub!(:load).and_return(@gem_list) + gem_resources = @provider.send(:gem_packages) + run4r = mock("Chef::Runner") + Chef::Runner.should_receive(:new).with(@node, an_instance_of(Chef::ResourceCollection)).and_return(run4r) + run4r.should_receive(:converge) + @provider.send(:install_gems) + end + + end + +end diff --git a/chef/spec/unit/provider/git_spec.rb b/chef/spec/unit/provider/git_spec.rb new file mode 100644 index 0000000000..5c75076e1e --- /dev/null +++ b/chef/spec/unit/provider/git_spec.rb @@ -0,0 +1,234 @@ +# +# Author:: Daniel DeLeo (<dan@kallistec.com>) +# Copyright:: Copyright (c) 2008 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper")) +describe Chef::Provider::Git do + + before(:each) do + @resource = Chef::Resource::Git.new("web2.0 app") + @resource.repository "git://github.com/opscode/chef.git" + @resource.destination "/my/deploy/dir" + @resource.revision "d35af14d41ae22b19da05d7d03a0bafc321b244c" + @node = Chef::Node.new + @provider = Chef::Provider::Git.new(@node, @resource) + end + + context "determining the revision of the currently deployed checkout" do + + before do + @stdout = mock("standard out") + @stderr = mock("standard error") + @exitstatus = mock("exitstatus") + end + + it "sets the current revison to nil if the deploy dir does not exist" do + ::File.should_receive(:exist?).with("/my/deploy/dir/.git").and_return(false) + @provider.find_current_revision.should be_nil + end + + it "determines the current revision when there is one" do + ::File.should_receive(:exist?).with("/my/deploy/dir/.git").and_return(true) + ::File.should_receive(:directory?).with("/my/deploy/dir").and_return(true) + ::Dir.should_receive(:chdir).with("/my/deploy/dir").and_yield + @stderr.stub!(:string).and_return('') + @stdout.stub!(:string).and_return("9b4d8dc38dd471246e7cfb1c3c1ad14b0f2bee13\n") + @exitstatus.stub!(:exitstatus).and_return(0) + @provider.should_receive(:popen4).and_yield("fake-pid","no-stdin", @stdout, @stderr).and_return(@exitstatus) + @provider.find_current_revision.should eql("9b4d8dc38dd471246e7cfb1c3c1ad14b0f2bee13") + end + + it "gives the current revision as nil when there is no current revision" do + ::File.should_receive(:exist?).with("/my/deploy/dir/.git").and_return(true) + ::File.should_receive(:directory?).with("/my/deploy/dir").and_return(true) + ::Dir.should_receive(:chdir).with("/my/deploy/dir").and_yield + @stderr.stub!(:string).and_return"fatal: Not a git repository (or any of the parent directories): .git" + @stdout.stub!(:string).and_return("") + @exitstatus.stub!(:exitstatus).and_return(128) + @provider.should_receive(:popen4).and_yield("fake-pid","no-stdin", @stdout, @stderr).and_return(@exitstatus) + @provider.find_current_revision.should be_nil + end + end + + it "creates a current_resource with the currently deployed revision when a clone exists in the destination dir" do + @provider.stub!(:find_current_revision).and_return("681c9802d1c62a45b490786c18f0b8216b309440") + @provider.load_current_resource + @provider.current_resource.name.should eql(@resource.name) + @provider.current_resource.revision.should eql("681c9802d1c62a45b490786c18f0b8216b309440") + end + + it "keeps the node and resource passed to it on initialize" do + @provider.node.should equal(@node) + @provider.new_resource.should equal(@resource) + end + + context "resolving revisions to a SHA" do + + before do + @stderr = mock("standard error") + @stderr.stub!(:string).and_return("") + @stdout = mock("std out") + @exitstatus = mock("exitstatus") + @exitstatus.stub!(:exitstatus).and_return(0) + @git_ls_remote = "git ls-remote git://github.com/opscode/chef.git " + end + + it "returns resource.revision as is if revision is already a full SHA" do + @provider.revision_sha.should eql("d35af14d41ae22b19da05d7d03a0bafc321b244c") + end + + it "converts resource.revision from a tag to a SHA" do + @resource.revision "v1.0" + @stdout.stub!(:string).and_return("503c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/0.8-alpha\n") + @provider.should_receive(:popen4).with(@git_ls_remote + "v1.0", {:cwd => instance_of(String)}). + and_yield("pid","stdin",@stdout,@stderr). + and_return(@exitstatus) + @provider.revision_sha.should eql("503c22a5e41f5ae3193460cca044ed1435029f53") + end + + it "raises a runtime error if you try to deploy from ``origin''" do + @resource.revision("origin") + lambda {@provider.revision_sha}.should raise_error(RuntimeError) + end + + it "raises a runtime error if the revision can't be resolved to any revision" do + @resource.revision "FAIL, that's the revision I want" + @stdout.stub!(:string).and_return("\n") + @provider.should_receive(:popen4).and_yield("pid","stdin",@stdout,@stderr).and_return(@exitstatus) + lambda {@provider.revision_sha}.should raise_error(RuntimeError) + end + + it "gives the latest HEAD revision SHA if nothing is specified" do + lots_of_shas = "28af684d8460ba4793eda3e7ac238c864a5d029a\tHEAD\n"+ + "503c22a5e41f5ae3193460cca044ed1435029f53\trefs/heads/0.8-alpha\n"+ + "28af684d8460ba4793eda3e7ac238c864a5d029a\trefs/heads/master\n"+ + "c44fe79bb5e36941ce799cee6b9de3a2ef89afee\trefs/tags/0.5.2\n"+ + "14534f0e0bf133dc9ff6dbe74f8a0c863ff3ac6d\trefs/tags/0.5.4\n"+ + "d36fddb4291341a1ff2ecc3c560494e398881354\trefs/tags/0.5.6\n"+ + "9e5ce9031cbee81015de680d010b603bce2dd15f\trefs/tags/0.6.0\n"+ + "9b4d8dc38dd471246e7cfb1c3c1ad14b0f2bee13\trefs/tags/0.6.2\n"+ + "014a69af1cdce619de82afaf6cdb4e6ac658fede\trefs/tags/0.7.0\n"+ + "fa8097ff666af3ce64761d8e1f1c2aa292a11378\trefs/tags/0.7.2\n"+ + "44f9be0b33ba5c10027ddb030a5b2f0faa3eeb8d\trefs/tags/0.7.4\n"+ + "d7b9957f67236fa54e660cc3ab45ffecd6e0ba38\trefs/tags/0.7.8\n"+ + "b7d19519a1c15f1c1a324e2683bd728b6198ce5a\trefs/tags/0.7.8^{}\n"+ + "ebc1b392fe7e8f0fbabc305c299b4d365d2b4d9b\trefs/tags/chef-server-package" + @resource.revision '' + @stdout.stub(:string).and_return(lots_of_shas) + @provider.should_receive(:popen4).and_yield("pid","stdin",@stdout,@stderr).and_return(@exitstatus) + @provider.revision_sha.should eql("28af684d8460ba4793eda3e7ac238c864a5d029a") + end + end + + it "responds to :revision_slug as an alias for revision_sha" do + @provider.should respond_to(:revision_slug) + end + + it "runs a clone command with default git options" do + @resource.user "deployNinja" + @resource.ssh_wrapper "do_it_this_way.sh" + expected_cmd = 'git clone git://github.com/opscode/chef.git /my/deploy/dir' + @provider.should_receive(:run_command).with(:command => expected_cmd, :user => "deployNinja", + :environment =>{"GIT_SSH"=>"do_it_this_way.sh"}) + @provider.clone + end + + it "compiles a clone command using --depth for shallow cloning" do + @resource.depth 5 + expected_cmd = 'git clone --depth 5 git://github.com/opscode/chef.git /my/deploy/dir' + @provider.should_receive(:run_command).with(:command => expected_cmd) + @provider.clone + end + + it "compiles a clone command with a remote other than ``origin''" do + @resource.remote "opscode" + expected_cmd = 'git clone -o opscode git://github.com/opscode/chef.git /my/deploy/dir' + @provider.should_receive(:run_command).with(:command => expected_cmd) + @provider.clone + end + + it "runs a checkout command with default options" do + expected_cmd = 'git checkout -b deploy d35af14d41ae22b19da05d7d03a0bafc321b244c' + @provider.should_receive(:run_command).with(:command => expected_cmd, :cwd => "/my/deploy/dir") + @provider.checkout + end + + it "runs an enable_submodule command" do + @resource.enable_submodules true + expected_cmd = "git submodule init && git submodule update" + @provider.should_receive(:run_command).with(:command => expected_cmd, :cwd => "/my/deploy/dir") + @provider.enable_submodules + end + + it "does nothing for enable_submodules if resource.enable_submodules #=> false" do + @provider.should_not_receive(:run_command) + @provider.enable_submodules + end + + it "runs a sync command with default options" do + expected_cmd = "git fetch origin && git reset --hard d35af14d41ae22b19da05d7d03a0bafc321b244c" + @provider.should_receive(:run_command).with(:command=>expected_cmd, :cwd=> "/my/deploy/dir") + @provider.sync + end + + it "compiles a sync command using remote tracking branches when remote is not ``origin''" do + @resource.remote "opscode" + expected_cmd = "git config remote.opscode.url git://github.com/opscode/chef.git && " + + "git config remote.opscode.fetch +refs/heads/*:refs/remotes/opscode/* && " + + "git fetch opscode && git reset --hard d35af14d41ae22b19da05d7d03a0bafc321b244c" + @provider.should_receive(:run_command).with(:command => expected_cmd, :cwd => "/my/deploy/dir") + @provider.sync + end + + it "does a checkout running the clone command then running the after clone command from the destination dir" do + @provider.should_receive(:clone) + @provider.should_receive(:checkout) + @provider.should_receive(:enable_submodules) + @provider.action_checkout + end + + it "does a sync by running the sync command" do + ::File.stub!(:exist?).with("/my/deploy/dir").and_return(true) + ::Dir.stub!(:entries).and_return(['.','..',"lib", "spec"]) + @provider.should_receive(:sync) + @provider.action_sync + end + + it "does a checkout instead of sync if the deploy directory doesn't exist" do + ::File.stub!(:exist?).with("/my/deploy/dir").and_return(false) + @provider.should_receive(:action_checkout) + @provider.should_not_receive(:run_command) + @provider.action_sync + end + + it "does a checkout instead of sync if the deploy directory is empty" do + ::File.stub!(:exist?).with("/my/deploy/dir").and_return(true) + ::Dir.stub!(:entries).with("/my/deploy/dir").and_return([".",".."]) + @provider.stub!(:sync_command).and_return("huzzah!") + @provider.should_receive(:action_checkout) + @provider.should_not_receive(:run_command).with(:command => "huzzah!", :cwd => "/my/deploy/dir") + @provider.action_sync + end + + it "does an export by cloning the repo then removing the .git directory" do + @provider.should_receive(:action_checkout) + FileUtils.should_receive(:rm_rf).with(@resource.destination + "/.git") + @provider.action_export + end + +end
\ No newline at end of file diff --git a/chef/spec/unit/provider/group/gpasswd_spec.rb b/chef/spec/unit/provider/group/gpasswd_spec.rb new file mode 100644 index 0000000000..342db08ce8 --- /dev/null +++ b/chef/spec/unit/provider/group/gpasswd_spec.rb @@ -0,0 +1,107 @@ +# +# Author:: AJ Christensen (<aj@opscode.com>) +# Copyright:: Copyright (c) 2008 OpsCode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "spec_helper")) + +describe Chef::Provider::Group::Gpasswd, "modify_group_members" do + before do + @node = mock("Chef::Node", :null_object => true) + @new_resource = mock("Chef::Resource::Group", + :null_object => true, + :group_name => "aj", + :members => [ "all", "your", "base" ], + :append => false + ) + @new_resource.stub!(:to_s).and_return("group[aj]") + @provider = Chef::Provider::Group::Gpasswd.new(@node, @new_resource) + @provider.stub!(:run_command).and_return(true) + end + + describe "with an empty members array" do + before do + @new_resource.stub!(:members).and_return([]) + end + + it "should log an appropriate message" do + Chef::Log.should_receive(:debug).with("group[aj]: not changing group members, the group has no members") + @provider.modify_group_members + end + end + + describe "with supplied members" do + before do + @new_resource.stub!(:members).and_return(["all", "your", "base"]) + end + + it "should log an appropriate debug message" do + Chef::Log.should_receive(:debug).with("group[aj]: setting group members to all, your, base") + @provider.modify_group_members + end + + it "should run gpasswd with the members joined by ',' followed by the target group" do + @provider.should_receive(:run_command).with({:command => "gpasswd -M all,your,base aj"}) + @provider.modify_group_members + end + + it "should run gpasswd individually for each user when the append option is set" do + @new_resource.stub!(:append).and_return(true) + @provider.should_receive(:run_command).with({:command => "gpasswd -a all aj"}) + @provider.should_receive(:run_command).with({:command => "gpasswd -a your aj"}) + @provider.should_receive(:run_command).with({:command => "gpasswd -a base aj"}) + @provider.modify_group_members + end + + end +end + +describe Chef::Provider::Group::Gpasswd, "load_current_resource" do + before do + @node = mock("Chef::Node", :null_object => true) + @new_resource = mock("Chef::Resource::Group", :null_object => true, :group_name => "aj") + @provider = Chef::Provider::Group::Gpasswd.new(@node, @new_resource) + File.stub!(:exists?).and_return(false) + end + + it "should raise an error if the required binary /usr/sbin/groupadd doesn't exist" do + File.should_receive(:exists?).with("/usr/sbin/groupadd").and_return(false) + lambda { @provider.load_current_resource }.should raise_error(Chef::Exceptions::Group) + end + it "should raise an error if the required binary /usr/sbin/groupmod doesn't exist" do + File.should_receive(:exists?).with("/usr/sbin/groupadd").and_return(true) + File.should_receive(:exists?).with("/usr/sbin/groupmod").and_return(false) + lambda { @provider.load_current_resource }.should raise_error(Chef::Exceptions::Group) + end + it "should raise an error if the required binary /usr/sbin/groupdel doesn't exist" do + File.should_receive(:exists?).with("/usr/sbin/groupadd").and_return(true) + File.should_receive(:exists?).with("/usr/sbin/groupmod").and_return(true) + File.should_receive(:exists?).with("/usr/sbin/groupdel").and_return(false) + lambda { @provider.load_current_resource }.should raise_error(Chef::Exceptions::Group) + end + it "should raise an error if the required binary /usr/bin/gpasswd doesn't exist" do + File.should_receive(:exists?).with("/usr/sbin/groupadd").and_return(true) + File.should_receive(:exists?).with("/usr/sbin/groupmod").and_return(true) + File.should_receive(:exists?).with("/usr/sbin/groupdel").and_return(true) + File.should_receive(:exists?).with("/usr/bin/gpasswd").and_return(false) + lambda { @provider.load_current_resource }.should raise_error(Chef::Exceptions::Group) + end + + it "shouldn't raise an error if the required binaries exist" do + File.stub!(:exists?).and_return(true) + lambda { @provider.load_current_resource }.should_not raise_error(Chef::Exceptions::Group) + end +end diff --git a/chef/spec/unit/provider/group/groupadd_spec.rb b/chef/spec/unit/provider/group/groupadd_spec.rb index e0fb144b93..8b7cb4e6e1 100644 --- a/chef/spec/unit/provider/group/groupadd_spec.rb +++ b/chef/spec/unit/provider/group/groupadd_spec.rb @@ -136,65 +136,34 @@ describe Chef::Provider::Group::Groupadd, "modify_group_members" do @provider = Chef::Provider::Group::Groupadd.new(@node, @new_resource) @provider.stub!(:run_command).and_return(true) end - - describe "with an empty members array" do - before do - @new_resource.stub!(:members).and_return([]) - end - - it "should log an appropriate message" do - Chef::Log.should_receive(:debug).with("group[aj]: not changing group members, the group has no members") - @provider.modify_group_members - end - end - - describe "with supplied members" do - before do - @new_resource.stub!(:members).and_return(["all", "your", "base"]) - end - - it "should log an appropriate debug message" do - Chef::Log.should_receive(:debug).with("group[aj]: setting group members to all, your, base") - @provider.modify_group_members - end - - it "should run gpasswd with the members joined by ',' followed by the target group" do - @provider.should_receive(:run_command).with({:command => "gpasswd -M all,your,base aj"}) - @provider.modify_group_members - end - - it "should run gpasswd individually for each user when the append option is set" do - @new_resource.stub!(:append).and_return(true) - @provider.should_receive(:run_command).with({:command => "gpasswd -a all aj"}) - @provider.should_receive(:run_command).with({:command => "gpasswd -a your aj"}) - @provider.should_receive(:run_command).with({:command => "gpasswd -a base aj"}) - @provider.modify_group_members - end - + + it "should raise an error when calling modify_group_members" do + lambda { @provider.modify_group_members }.should raise_error(Chef::Exceptions::Group, "you must override modify_group_members in #{@provider.to_s}") end end -describe Chef::Provider::Group::Groupadd, "load_current_resource" do +describe Chef::Provider::Group::Usermod, "load_current_resource" do before do @node = mock("Chef::Node", :null_object => true) Chef::Node.stub!(:new).and_return(@node) @new_resource = mock("Chef::Resource::Group", :null_object => true, :group_name => "aj") - @provider = Chef::Provider::Group::Groupadd.new(@node, @new_resource) + @provider = Chef::Provider::Group::Usermod.new(@node, @new_resource) File.stub!(:exists?).and_return(false) end - [ "/usr/sbin/groupadd", - "/usr/sbin/groupmod", - "/usr/sbin/groupdel", - "/usr/bin/gpasswd" ].each do |required_binary| - it "should raise an error if the required binary #{required_binary} doesn't exist" do - File.should_receive(:exists?).with("/usr/sbin/groupadd").and_return(false) - lambda { @provider.load_current_resource }.should raise_error(Chef::Exceptions::Group) - end - end - - it "shouldn't raise an error if the required binaries exist" do - File.stub!(:exists?).and_return(true) - lambda { @provider.load_current_resource }.should_not raise_error(Chef::Exceptions::Group) + it "should raise an error if the required binary /usr/sbin/groupadd doesn't exist" do + File.should_receive(:exists?).with("/usr/sbin/groupadd").and_return(false) + lambda { @provider.load_current_resource }.should raise_error(Chef::Exceptions::Group) + end + it "should raise an error if the required binary /usr/sbin/groupmod doesn't exist" do + File.should_receive(:exists?).with("/usr/sbin/groupadd").and_return(true) + File.should_receive(:exists?).with("/usr/sbin/groupmod").and_return(false) + lambda { @provider.load_current_resource }.should raise_error(Chef::Exceptions::Group) + end + it "should raise an error if the required binary /usr/sbin/groupdel doesn't exist" do + File.should_receive(:exists?).with("/usr/sbin/groupadd").and_return(true) + File.should_receive(:exists?).with("/usr/sbin/groupmod").and_return(true) + File.should_receive(:exists?).with("/usr/sbin/groupdel").and_return(false) + lambda { @provider.load_current_resource }.should raise_error(Chef::Exceptions::Group) end end diff --git a/chef/spec/unit/provider/group/usermod_spec.rb b/chef/spec/unit/provider/group/usermod_spec.rb new file mode 100644 index 0000000000..05f6ca345d --- /dev/null +++ b/chef/spec/unit/provider/group/usermod_spec.rb @@ -0,0 +1,109 @@ +# +# Author:: AJ Christensen (<aj@opscode.com>) +# Copyright:: Copyright (c) 2008 OpsCode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "..", "spec_helper")) + +describe Chef::Provider::Group::Usermod, "modify_group_members" do + before do + @node = mock("Chef::Node", :null_object => true) + @new_resource = mock("Chef::Resource::Group", + :null_object => true, + :group_name => "aj", + :members => [ "all", "your", "base" ], + :append => false + ) + @new_resource.stub!(:to_s).and_return("group[aj]") + @provider = Chef::Provider::Group::Usermod.new(@node, @new_resource) + @provider.stub!(:run_command).and_return(true) + end + + describe "with an empty members array" do + before do + @new_resource.stub!(:members).and_return([]) + end + + it "should log an appropriate message" do + Chef::Log.should_receive(:debug).with("group[aj]: not changing group members, the group has no members") + @provider.modify_group_members + end + end + + describe "with supplied members" do + platforms = { + "openbsd" => "-G", + "netbsd" => "-G", + "solaris" => "-a -G" + } + + before do + @new_resource.stub!(:members).and_return(["all", "your", "base"]) + end + + it "should raise an error when setting the entire group directly" do + lambda { @provider.modify_group_members }.should raise_error(Chef::Exceptions::Group, "setting group members directly is not supported by #{@provider.to_s}") + end + + platforms.each do |platform, flags| + it "should usermod each user when the append option is set on #{platform}" do + @node.stub!(:[]).with(:platform).and_return(platform) + @new_resource.stub!(:append).and_return(true) + @provider.should_receive(:run_command).with({:command => "usermod #{flags} aj all"}) + @provider.should_receive(:run_command).with({:command => "usermod #{flags} aj your"}) + @provider.should_receive(:run_command).with({:command => "usermod #{flags} aj base"}) + @provider.modify_group_members + end + end + end +end + +describe Chef::Provider::Group::Usermod, "load_current_resource" do + before do + @node = mock("Chef::Node", :null_object => true) + @new_resource = mock("Chef::Resource::Group", :null_object => true, :group_name => "aj") + @provider = Chef::Provider::Group::Usermod.new(@node, @new_resource) + File.stub!(:exists?).and_return(false) + end + + it "should raise an error if the required binary /usr/sbin/groupadd doesn't exist" do + File.should_receive(:exists?).with("/usr/sbin/groupadd").and_return(false) + lambda { @provider.load_current_resource }.should raise_error(Chef::Exceptions::Group) + end + it "should raise an error if the required binary /usr/sbin/groupmod doesn't exist" do + File.should_receive(:exists?).with("/usr/sbin/groupadd").and_return(true) + File.should_receive(:exists?).with("/usr/sbin/groupmod").and_return(false) + lambda { @provider.load_current_resource }.should raise_error(Chef::Exceptions::Group) + end + it "should raise an error if the required binary /usr/sbin/groupdel doesn't exist" do + File.should_receive(:exists?).with("/usr/sbin/groupadd").and_return(true) + File.should_receive(:exists?).with("/usr/sbin/groupmod").and_return(true) + File.should_receive(:exists?).with("/usr/sbin/groupdel").and_return(false) + lambda { @provider.load_current_resource }.should raise_error(Chef::Exceptions::Group) + end + it "should raise an error if the required binary /usr/sbin/usermod doesn't exist" do + File.should_receive(:exists?).with("/usr/sbin/groupadd").and_return(true) + File.should_receive(:exists?).with("/usr/sbin/groupmod").and_return(true) + File.should_receive(:exists?).with("/usr/sbin/groupdel").and_return(true) + File.should_receive(:exists?).with("/usr/sbin/usermod").and_return(false) + lambda { @provider.load_current_resource }.should raise_error(Chef::Exceptions::Group) + end + + it "shouldn't raise an error if the required binaries exist" do + File.stub!(:exists?).and_return(true) + lambda { @provider.load_current_resource }.should_not raise_error(Chef::Exceptions::Group) + end +end diff --git a/chef/spec/unit/provider/package/apt_spec.rb b/chef/spec/unit/provider/package/apt_spec.rb index 9a81c7ea61..cd4d1db3da 100644 --- a/chef/spec/unit/provider/package/apt_spec.rb +++ b/chef/spec/unit/provider/package/apt_spec.rb @@ -136,7 +136,7 @@ describe Chef::Provider::Package::Apt, "install_package" do end it "should run apt-get install with the package name and version" do - @provider.should_receive(:run_command).with({ + @provider.should_receive(:run_command_with_systems_locale).with({ :command => "apt-get -q -y install emacs=1.0", :environment => { "DEBIAN_FRONTEND" => "noninteractive" @@ -146,7 +146,7 @@ describe Chef::Provider::Package::Apt, "install_package" do end it "should run apt-get install with the package name and version and options if specified" do - @provider.should_receive(:run_command).with({ + @provider.should_receive(:run_command_with_systems_locale).with({ :command => "apt-get -q -y --force-yes install emacs=1.0", :environment => { "DEBIAN_FRONTEND" => "noninteractive" @@ -194,7 +194,7 @@ describe Chef::Provider::Package::Apt, "remove_package" do end it "should run apt-get remove with the package name" do - @provider.should_receive(:run_command).with({ + @provider.should_receive(:run_command_with_systems_locale).with({ :command => "apt-get -q -y remove emacs", :environment => { "DEBIAN_FRONTEND" => "noninteractive" @@ -204,7 +204,7 @@ describe Chef::Provider::Package::Apt, "remove_package" do end it "should run apt-get remove with the package name and options if specified" do - @provider.should_receive(:run_command).with({ + @provider.should_receive(:run_command_with_systems_locale).with({ :command => "apt-get -q -y --force-yes remove emacs", :environment => { "DEBIAN_FRONTEND" => "noninteractive" @@ -231,7 +231,7 @@ describe Chef::Provider::Package::Apt, "purge_package" do end it "should run apt-get purge with the package name" do - @provider.should_receive(:run_command).with({ + @provider.should_receive(:run_command_with_systems_locale).with({ :command => "apt-get -q -y purge emacs", :environment => { "DEBIAN_FRONTEND" => "noninteractive" @@ -241,7 +241,7 @@ describe Chef::Provider::Package::Apt, "purge_package" do end it "should run apt-get purge with the package name and options if specified" do - @provider.should_receive(:run_command).with({ + @provider.should_receive(:run_command_with_systems_locale).with({ :command => "apt-get -q -y --force-yes purge emacs", :environment => { "DEBIAN_FRONTEND" => "noninteractive" @@ -266,7 +266,7 @@ describe Chef::Provider::Package::Apt, "preseed_package" do ) @provider = Chef::Provider::Package::Apt.new(@node, @new_resource) @provider.stub!(:get_preseed_file).and_return("/tmp/emacs-10.seed") - @provider.stub!(:run_command).and_return(true) + @provider.stub!(:run_command_with_systems_locale).and_return(true) end it "should get the full path to the preseed response file" do @@ -275,7 +275,7 @@ describe Chef::Provider::Package::Apt, "preseed_package" do end it "should run debconf-set-selections on the preseed file if it has changed" do - @provider.should_receive(:run_command).with({ + @provider.should_receive(:run_command_with_systems_locale).with({ :command => "debconf-set-selections /tmp/emacs-10.seed", :environment => { "DEBIAN_FRONTEND" => "noninteractive" @@ -286,7 +286,7 @@ describe Chef::Provider::Package::Apt, "preseed_package" do it "should not run debconf-set-selections if the preseed file has not changed" do @provider.stub!(:get_preseed_file).and_return(false) - @provider.should_not_receive(:run_command) + @provider.should_not_receive(:run_command_with_systems_locale) @provider.preseed_package("emacs", "10") end end diff --git a/chef/spec/unit/provider/package/dpkg_spec.rb b/chef/spec/unit/provider/package/dpkg_spec.rb index fb2eac897b..74865a1b5d 100644 --- a/chef/spec/unit/provider/package/dpkg_spec.rb +++ b/chef/spec/unit/provider/package/dpkg_spec.rb @@ -29,16 +29,8 @@ describe Chef::Provider::Package::Dpkg, "load_current_resource" do :updated => nil, :source => "/tmp/wget_1.11.4-1ubuntu1_amd64.deb" ) - @current_resource = mock("Chef::Resource::Package", - :null_object => true, - :name => "wget", - :version => nil, - :package_name => nil, - :updated => nil - ) @provider = Chef::Provider::Package::Dpkg.new(@node, @new_resource) - Chef::Resource::Package.stub!(:new).and_return(@current_resource) @stdin = mock("STDIN", :null_object => true) @stdout = mock("STDOUT", :null_object => true) @@ -51,15 +43,10 @@ describe Chef::Provider::Package::Dpkg, "load_current_resource" do 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.load_current_resource + @provider.current_resource.package_name.should == "wget" end - it "should set the current resources package name to the new resources package name" do - @current_resource.should_receive(:package_name).with(@new_resource.package_name) - @provider.load_current_resource - end - it "should raise an exception if a source is supplied but not found" do ::File.stub!(:exists?).and_return(false) lambda { @provider.load_current_resource }.should raise_error(Chef::Exceptions::Package) @@ -68,9 +55,16 @@ describe Chef::Provider::Package::Dpkg, "load_current_resource" do it "should get the source package version from dpkg-deb if provided" do @stdout.stub!(:each).and_yield("wget\t1.11.4-1ubuntu1") @provider.stub!(:popen4).with("dpkg-deb -W #{@new_resource.source}").and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) - @current_resource.should_receive(:package_name).with("wget") @new_resource.should_receive(:version).with("1.11.4-1ubuntu1") @provider.load_current_resource + @provider.current_resource.package_name.should == "wget" + end + + it "gets the source package name from dpkg-deb correctly when the package name has `-' or `+' characters" do + @stdout.stub!(:each).and_yield("foo-pkg++2\t1.11.4-1ubuntu1") + @provider.stub!(:popen4).and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) + @provider.load_current_resource + @provider.current_resource.package_name.should == "foo-pkg++2" end it "should raise an exception if the source is not set but we are installing" do @@ -99,11 +93,12 @@ describe Chef::Provider::Package::Dpkg, "load_current_resource" do and_yield("Config-Version: 1.11.4-1ubuntu1"). and_yield("Depends: libc6 (>= 2.8~20080505), libssl0.9.8 (>= 0.9.8f-5)"). and_yield("Conflicts: wget-ssl") - @provider.stub!(:popen4).with("dpkg -s #{@current_resource.package_name}").and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) - @current_resource.should_receive(:version).with("1.11.4-1ubuntu1") + @provider.stub!(:popen4).with("dpkg -s wget").and_yield(@pid, @stdin, @stdout, @stderr).and_return(@status) + @provider.load_current_resource + @provider.current_resource.version.should == "1.11.4-1ubuntu1" end - + it "should raise an exception if dpkg fails to run" do @status = mock("Status", :exitstatus => -1) @provider.stub!(:popen4).and_return(@status) @@ -127,22 +122,20 @@ describe Chef::Provider::Package::Dpkg, "install and upgrade" do end it "should run dpkg -i with the package source" do - @provider.should_receive(:run_command).with({ + @provider.should_receive(:run_command_with_systems_locale).with({ :command => "dpkg -i /tmp/wget_1.11.4-1ubuntu1_amd64.deb", :environment => { - "DEBIAN_FRONTEND" => "noninteractive", - "LANG" => "en_US" + "DEBIAN_FRONTEND" => "noninteractive" } }) @provider.install_package("wget", "1.11.4-1ubuntu1") end it "should run dpkg -i with the package source and options if specified" do - @provider.should_receive(:run_command).with({ + @provider.should_receive(:run_command_with_systems_locale).with({ :command => "dpkg -i --force-yes /tmp/wget_1.11.4-1ubuntu1_amd64.deb", :environment => { - "DEBIAN_FRONTEND" => "noninteractive", - "LANG" => "en_US" + "DEBIAN_FRONTEND" => "noninteractive" } }) @new_resource.stub!(:options).and_return("--force-yes") @@ -170,22 +163,20 @@ describe Chef::Provider::Package::Dpkg, "remove and purge" do end it "should run dpkg -r to remove the package" do - @provider.should_receive(:run_command).with({ + @provider.should_receive(:run_command_with_systems_locale).with({ :command => "dpkg -r wget", :environment => { - "DEBIAN_FRONTEND" => "noninteractive", - "LANG" => "en_US" + "DEBIAN_FRONTEND" => "noninteractive" } }) @provider.remove_package("wget", "1.11.4-1ubuntu1") end it "should run dpkg -r to remove the package with options if specified" do - @provider.should_receive(:run_command).with({ + @provider.should_receive(:run_command_with_systems_locale).with({ :command => "dpkg -r --force-yes wget", :environment => { - "DEBIAN_FRONTEND" => "noninteractive", - "LANG" => "en_US" + "DEBIAN_FRONTEND" => "noninteractive" } }) @new_resource.stub!(:options).and_return("--force-yes") @@ -194,22 +185,20 @@ describe Chef::Provider::Package::Dpkg, "remove and purge" do end it "should run dpkg -P to purge the package" do - @provider.should_receive(:run_command).with({ + @provider.should_receive(:run_command_with_systems_locale).with({ :command => "dpkg -P wget", :environment => { - "DEBIAN_FRONTEND" => "noninteractive", - "LANG" => "en_US" + "DEBIAN_FRONTEND" => "noninteractive" } }) @provider.purge_package("wget", "1.11.4-1ubuntu1") end it "should run dpkg -P to purge the package with options if specified" do - @provider.should_receive(:run_command).with({ + @provider.should_receive(:run_command_with_systems_locale).with({ :command => "dpkg -P --force-yes wget", :environment => { - "DEBIAN_FRONTEND" => "noninteractive", - "LANG" => "en_US" + "DEBIAN_FRONTEND" => "noninteractive" } }) @new_resource.stub!(:options).and_return("--force-yes") diff --git a/chef/spec/unit/provider/package/freebsd_spec.rb b/chef/spec/unit/provider/package/freebsd_spec.rb index 80460d0e29..b75a34eba1 100644 --- a/chef/spec/unit/provider/package/freebsd_spec.rb +++ b/chef/spec/unit/provider/package/freebsd_spec.rb @@ -138,7 +138,7 @@ describe Chef::Provider::Package::Freebsd, "install_package" do end it "should run pkg_add -r with the package name" do - @provider.should_receive(:run_command).with({ + @provider.should_receive(:run_command_with_systems_locale).with({ :command => "pkg_add -r zsh", }) @provider.install_package("zsh", "4.3.6_7") @@ -147,28 +147,34 @@ describe Chef::Provider::Package::Freebsd, "install_package" do it "should run make install when installing from ports" do @new_resource.stub!(:source).and_return("ports") @provider.should_receive(:port_path).and_return("/usr/ports/shells/zsh") - @provider.should_receive(:run_command).with(:command => "make -DBATCH install", :cwd => "/usr/ports/shells/zsh") + @provider.should_receive(:run_command_with_systems_locale).with(:command => "make -DBATCH install", :cwd => "/usr/ports/shells/zsh") @provider.install_package("zsh", "4.3.6_7") end end describe Chef::Provider::Package::Freebsd, "port path" do it "should figure out the port path from the package_name using whereis" do - @new_resource = mock("Chef::Resource::Package", :package_name => "zsh") + @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") + @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" 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") + @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" @@ -198,13 +204,13 @@ describe Chef::Provider::Package::Freebsd, "ruby-iconv (package with a dash in t end it "should run pkg_add -r with the package name" do - @provider.should_receive(:run_command).with(:command => "pkg_add -r ruby18-iconv") + @provider.should_receive(:run_command_with_systems_locale).with(:command => "pkg_add -r ruby18-iconv") @provider.install_package("ruby-iconv", "1.0") end it "should run make install when installing from ports" do @new_resource.stub!(:source).and_return("ports") - @provider.should_receive(:run_command).with(:command => "make -DBATCH install", :cwd => "/usr/ports/converters/ruby-iconv") + @provider.should_receive(:run_command_with_systems_locale).with(:command => "make -DBATCH install", :cwd => "/usr/ports/converters/ruby-iconv") @provider.install_package("ruby-iconv", "1.0") end end @@ -230,7 +236,7 @@ describe Chef::Provider::Package::Freebsd, "remove_package" do end it "should run pkg_delete with the package name and version" do - @provider.should_receive(:run_command).with({ + @provider.should_receive(:run_command_with_systems_locale).with({ :command => "pkg_delete zsh-4.3.6_7" }) @provider.remove_package("zsh", "4.3.6_7") @@ -274,7 +280,7 @@ describe Chef::Provider::Package::Freebsd, "install_package latest link fixes" d @provider.stub!(:package_name).and_return("perl") @provider.stub!(:latest_link_name).and_return("perl") - @provider.should_receive(:run_command).with({ + @provider.should_receive(:run_command_with_systems_locale).with({ :command => "pkg_add -r perl", }) @provider.install_package("perl5.8", "5.8.8_1") @@ -299,7 +305,7 @@ describe Chef::Provider::Package::Freebsd, "install_package latest link fixes" d @provider.stub!(:package_name).and_return("mysql-server") @provider.stub!(:latest_link_name).and_return("mysql50-server") - @provider.should_receive(:run_command).with({ + @provider.should_receive(:run_command_with_systems_locale).with({ :command => "pkg_add -r mysql50-server", }) @provider.install_package("mysql50-server", "5.0.45_1") diff --git a/chef/spec/unit/provider/package/macports_spec.rb b/chef/spec/unit/provider/package/macports_spec.rb index ca004116ac..c289dd4c14 100644 --- a/chef/spec/unit/provider/package/macports_spec.rb +++ b/chef/spec/unit/provider/package/macports_spec.rb @@ -113,7 +113,7 @@ EOF it "should run the port install command with the correct version" do @current_resource.should_receive(:version).and_return("4.1.6") @provider.current_resource = @current_resource - @provider.should_receive(:run_command).with(:command => "port install zsh @4.2.7") + @provider.should_receive(:run_command_with_systems_locale).with(:command => "port install zsh @4.2.7") @provider.install_package("zsh", "4.2.7") end @@ -121,7 +121,7 @@ EOF it "should not do anything if a package already exists with the same version" do @current_resource.should_receive(:version).and_return("4.2.7") @provider.current_resource = @current_resource - @provider.should_not_receive(:run_command) + @provider.should_not_receive(:run_command_with_systems_locale) @provider.install_package("zsh", "4.2.7") end @@ -129,24 +129,24 @@ EOF describe "purge_package" do it "should run the port uninstall command with the correct version" do - @provider.should_receive(:run_command).with(:command => "port uninstall zsh @4.2.7") + @provider.should_receive(:run_command_with_systems_locale).with(:command => "port uninstall zsh @4.2.7") @provider.purge_package("zsh", "4.2.7") end it "should purge the currently active version if no explicit version is passed in" do - @provider.should_receive(:run_command).with(:command => "port uninstall zsh") + @provider.should_receive(:run_command_with_systems_locale).with(:command => "port uninstall zsh") @provider.purge_package("zsh", nil) end end describe "remove_package" do it "should run the port deactivate command with the correct version" do - @provider.should_receive(:run_command).with(:command => "port deactivate zsh @4.2.7") + @provider.should_receive(:run_command_with_systems_locale).with(:command => "port deactivate zsh @4.2.7") @provider.remove_package("zsh", "4.2.7") end it "should remove the currently active version if no explicit version is passed in" do - @provider.should_receive(:run_command).with(:command => "port deactivate zsh") + @provider.should_receive(:run_command_with_systems_locale).with(:command => "port deactivate zsh") @provider.remove_package("zsh", nil) end end @@ -156,7 +156,7 @@ EOF @current_resource.should_receive(:version).at_least(:once).and_return("4.1.6") @provider.current_resource = @current_resource - @provider.should_receive(:run_command).with(:command => "port upgrade zsh @4.2.7") + @provider.should_receive(:run_command_with_systems_locale).with(:command => "port upgrade zsh @4.2.7") @provider.upgrade_package("zsh", "4.2.7") end @@ -164,7 +164,7 @@ EOF it "should not run the port upgrade command if the version is already installed" do @current_resource.should_receive(:version).at_least(:once).and_return("4.2.7") @provider.current_resource = @current_resource - @provider.should_not_receive(:run_command) + @provider.should_not_receive(:run_command_with_systems_locale) @provider.upgrade_package("zsh", "4.2.7") end diff --git a/chef/spec/unit/provider/package/portage_spec.rb b/chef/spec/unit/provider/package/portage_spec.rb index 2ac214dec3..b5a37db254 100644 --- a/chef/spec/unit/provider/package/portage_spec.rb +++ b/chef/spec/unit/provider/package/portage_spec.rb @@ -117,21 +117,21 @@ describe Chef::Provider::Package::Portage, "install_package" do end it "should install a normally versioned package using portage" do - @provider.should_receive(:run_command).with({ + @provider.should_receive(:run_command_with_systems_locale).with({ :command => "emerge -g --color n --nospinner --quiet =dev-util/git-1.0.0" }) @provider.install_package("dev-util/git", "1.0.0") end it "should install a tilde versioned package using portage" do - @provider.should_receive(:run_command).with({ + @provider.should_receive(:run_command_with_systems_locale).with({ :command => "emerge -g --color n --nospinner --quiet ~dev-util/git-1.0.0" }) @provider.install_package("dev-util/git", "~1.0.0") end it "should add options to the emerge command when specified" do - @provider.should_receive(:run_command).with({ + @provider.should_receive(:run_command_with_systems_locale).with({ :command => "emerge -g --color n --nospinner --quiet --oneshot =dev-util/git-1.0.0" }) @new_resource.stub!(:options).and_return("--oneshot") @@ -156,14 +156,14 @@ describe Chef::Provider::Package::Portage, "remove_package" do end it "should un-emerge the package with no version specified" do - @provider.should_receive(:run_command).with({ + @provider.should_receive(:run_command_with_systems_locale).with({ :command => "emerge --unmerge --color n --nospinner --quiet dev-util/git" }) @provider.remove_package("dev-util/git", nil) end it "should un-emerge the package with a version specified" do - @provider.should_receive(:run_command).with({ + @provider.should_receive(:run_command_with_systems_locale).with({ :command => "emerge --unmerge --color n --nospinner --quiet =dev-util/git-1.0.0" }) @provider.remove_package("dev-util/git", "1.0.0") diff --git a/chef/spec/unit/provider/package/rpm_spec.rb b/chef/spec/unit/provider/package/rpm_spec.rb index 8b999f8751..fea096033a 100644 --- a/chef/spec/unit/provider/package/rpm_spec.rb +++ b/chef/spec/unit/provider/package/rpm_spec.rb @@ -114,14 +114,14 @@ describe Chef::Provider::Package::Rpm, "install and upgrade" do end it "should run rpm -i with the package source to install" do - @provider.should_receive(:run_command).with({ + @provider.should_receive(:run_command_with_systems_locale).with({ :command => "rpm -i /tmp/emacs-21.4-20.el5.i386.rpm" }) @provider.install_package("emacs", "21.4-20.el5") end it "should run rpm -U with the package source to upgrade" do - @provider.should_receive(:run_command).with({ + @provider.should_receive(:run_command_with_systems_locale).with({ :command => "rpm -U /tmp/emacs-21.4-20.el5.i386.rpm" }) @provider.upgrade_package("emacs", "21.4-20.el5") @@ -142,7 +142,7 @@ describe Chef::Provider::Package::Rpm, "remove" do end it "should run rpm -e to remove the package" do - @provider.should_receive(:run_command).with({ + @provider.should_receive(:run_command_with_systems_locale).with({ :command => "rpm -e emacs-21.4-20.el5" }) @provider.remove_package("emacs", "21.4-20.el5") diff --git a/chef/spec/unit/provider/package/rubygems_spec.rb b/chef/spec/unit/provider/package/rubygems_spec.rb index c3b80c9015..b03bcd9104 100644 --- a/chef/spec/unit/provider/package/rubygems_spec.rb +++ b/chef/spec/unit/provider/package/rubygems_spec.rb @@ -60,8 +60,11 @@ describe Chef::Provider::Package::Rubygems, "install_package" 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\"" + :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 -end
\ No newline at end of file +end diff --git a/chef/spec/unit/provider/package/yum_spec.rb b/chef/spec/unit/provider/package/yum_spec.rb index 2e8ddb9971..11d33438dc 100644 --- a/chef/spec/unit/provider/package/yum_spec.rb +++ b/chef/spec/unit/provider/package/yum_spec.rb @@ -104,7 +104,7 @@ describe Chef::Provider::Package::Yum, "install_package" do end it "should run yum install with the package name and version" do - @provider.should_receive(:run_command).with({ + @provider.should_receive(:run_command_with_systems_locale).with({ :command => "yum -q -y install emacs-1.0" }) @provider.install_package("emacs", "1.0") @@ -143,7 +143,7 @@ describe Chef::Provider::Package::Yum, "upgrade_package" do end it "should run yum update if the package is installed" do - @provider.should_receive(:run_command).with({ + @provider.should_receive(:run_command_with_systems_locale).with({ :command => "yum -q -y update emacs-11" }) @provider.upgrade_package(@new_resource.name, @provider.candidate_version) @@ -151,7 +151,7 @@ describe Chef::Provider::Package::Yum, "upgrade_package" do it "should run yum install if the package is not installed" do @current_resource.stub!(:version).and_return(nil) - @provider.should_receive(:run_command).with({ + @provider.should_receive(:run_command_with_systems_locale).with({ :command => "yum -q -y install emacs-11" }) @provider.upgrade_package(@new_resource.name, @provider.candidate_version) @@ -180,7 +180,7 @@ describe Chef::Provider::Package::Yum, "remove_package" do end it "should run yum remove with the package name" do - @provider.should_receive(:run_command).with({ + @provider.should_receive(:run_command_with_systems_locale).with({ :command => "yum -q -y remove emacs-1.0" }) @provider.remove_package("emacs", "1.0") @@ -209,7 +209,7 @@ describe Chef::Provider::Package::Yum, "purge_package" do end it "should run yum remove with the package name" do - @provider.should_receive(:run_command).with({ + @provider.should_receive(:run_command_with_systems_locale).with({ :command => "yum -q -y remove emacs-1.0" }) @provider.purge_package("emacs", "1.0") diff --git a/chef/spec/unit/provider/remote_file_spec.rb b/chef/spec/unit/provider/remote_file_spec.rb index 82882a896a..cce78a5e2a 100644 --- a/chef/spec/unit/provider/remote_file_spec.rb +++ b/chef/spec/unit/provider/remote_file_spec.rb @@ -165,34 +165,57 @@ describe Chef::Provider::RemoteFile, "do_remote_file" do @provider.should_receive(:checksum).with(@tempfile.path).and_return("0fd012fdc96e96f8f7cf2046522a54aed0ce470224513e45da6bc1a17a4924aa") do_remote_file end + + describe "when the target file does not exist" do + before do + ::File.stub!(:exists?).with(@resource.path).and_return(false) + @provider.stub!(:get_from_server).and_return(@tempfile) + end + + it "should copy the raw file to the new resource" do + 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) + do_remote_file + end + end describe "when the target file already exists" do before do - ::File.stub!(:exists?).and_return(true) + ::File.stub!(:exists?).with(@resource.path).and_return(true) @provider.stub!(:get_from_server).and_return(@tempfile) end - it "should backup the original file if it is different" do - @provider.current_resource.checksum("0fd012fdc96e96f8f7cf2046522a54aed0ce470224513e45da6bc1a17a4924ab") - @provider.should_receive(:backup).with(@resource.path).and_return(true) - do_remote_file + describe "and the checksum doesn't match" do + before do + @provider. + current_resource. + checksum("0fd012fdc96e96f8f7cf2046522a54aed0ce470224513e45da6bc1a17a4924ab") + end + + it "should backup the original file" do + @provider.should_receive(:backup).with(@resource.path).and_return(true) + do_remote_file + end + + it "should copy the raw file to the new resource" do + 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) + do_remote_file + end end it "shouldn't backup the original file when it's the same" do @provider.should_not_receive(:backup).with(@resource.path).and_return(true) do_remote_file end - - end - - it "should set the new resource to updated" do - @resource.should_receive(:updated=).with(true) - do_remote_file - end - - it "should copy the raw file to the new resource" do - FileUtils.should_receive(:cp).with(@tempfile.path, @resource.path).and_return(true) - do_remote_file end it "should set the owner if provided" do diff --git a/chef/spec/unit/provider/service_spec.rb b/chef/spec/unit/provider/service_spec.rb index 2bd3acb6f2..2c667de4e2 100644 --- a/chef/spec/unit/provider/service_spec.rb +++ b/chef/spec/unit/provider/service_spec.rb @@ -51,17 +51,20 @@ describe Chef::Provider::Service, "action_enable" do @provider.stub!(:enable_service).and_return(true) end - it "should enable the service if disabled" do + it "should enable the service if disabled and set the resource as updated" do @current_resource.stub!(:enabled).and_return(false) @provider.should_receive(:enable_service).and_return(true) + @provider.new_resource.should_receive(:updated=).with(true) @provider.action_enable end it "should not enable the service if already enabled" do @current_resource.stub!(:enabled).and_return(true) @provider.should_not_receive(:enable_service).and_return(true) + @provider.new_resource.should_not_receive(:updated=).with(true) @provider.action_enable end + end describe Chef::Provider::Service, "action_disable" do @@ -84,15 +87,17 @@ describe Chef::Provider::Service, "action_disable" do @provider.stub!(:disable_service).and_return(true) end - it "should disable the service if enabled" do + it "should disable the service if enabled and set the resource as updated" do @current_resource.stub!(:enabled).and_return(true) @provider.should_receive(:disable_service).and_return(true) + @provider.new_resource.should_receive(:updated=).with(true) @provider.action_disable end it "should not disable the service if already disabled" do @current_resource.stub!(:enabled).and_return(false) @provider.should_not_receive(:disable_service).and_return(true) + @provider.new_resource.should_not_receive(:updated=).with(true) @provider.action_disable end end @@ -117,15 +122,17 @@ describe Chef::Provider::Service, "action_start" do @provider.stub!(:start_service).and_return(true) end - it "should start the service if it isn't running" do + it "should start the service if it isn't running and set the resource as updated" do @current_resource.stub!(:running).and_return(false) @provider.should_receive(:start_service).with.and_return(true) + @provider.new_resource.should_receive(:updated=).with(true) @provider.action_start end it "should not start the service if already running" do @current_resource.stub!(:running).and_return(true) @provider.should_not_receive(:start_service).and_return(true) + @provider.new_resource.should_not_receive(:updated=).with(true) @provider.action_enable end end @@ -150,15 +157,17 @@ describe Chef::Provider::Service, "action_stop" do @provider.stub!(:stop_service).and_return(true) end - it "should stop the service if it is running" do + it "should stop the service if it is running and set the resource as updated" do @current_resource.stub!(:running).and_return(true) @provider.should_receive(:stop_service).and_return(true) + @provider.new_resource.should_receive(:updated=).with(true) @provider.action_stop end it "should not stop the service if it's already stopped" do @current_resource.stub!(:running).and_return(false) @provider.should_not_receive(:stop_service).and_return(true) + @provider.new_resource.should_not_receive(:updated=).with(true) @provider.action_stop end end @@ -185,14 +194,16 @@ before(:each) do @current_resource.stub!(:supports).and_return({:restart => true}) end - it "should restart the service if it's supported" do + it "should restart the service if it's supported and set the resource as updated" do @provider.should_receive(:restart_service).and_return(true) + @provider.new_resource.should_receive(:updated=).with(true) @provider.action_restart end - it "should restart the service even if it isn't running" do + it "should restart the service even if it isn't running and set the resource as updated" do @current_resource.stub!(:running).and_return(false) @provider.should_receive(:restart_service).and_return(true) + @provider.new_resource.should_receive(:updated=).with(true) @provider.action_restart end end @@ -225,15 +236,17 @@ before(:each) do lambda { @provider.action_reload }.should raise_error(Chef::Exceptions::UnsupportedAction) end - it "should reload the service if it is running" do + it "should reload the service if it is running and set the resource as updated" do @current_resource.stub!(:running).and_return(true) @provider.should_receive(:reload_service).and_return(true) + @provider.new_resource.should_receive(:updated=).with(true) @provider.action_reload end it "should not reload the service if it's stopped" do @current_resource.stub!(:running).and_return(false) @provider.should_not_receive(:stop_service).and_return(true) + @provider.new_resource.should_not_receive(:updated=).with(true) @provider.action_stop end end diff --git a/chef/spec/unit/provider/subversion_spec.rb b/chef/spec/unit/provider/subversion_spec.rb new file mode 100644 index 0000000000..86624c137e --- /dev/null +++ b/chef/spec/unit/provider/subversion_spec.rb @@ -0,0 +1,192 @@ +# +# Author:: Daniel DeLeo (<dan@kallistec.com>) +# Copyright:: Copyright (c) 2008 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + + +require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper")) + +describe Chef::Provider::Subversion do + + before do + @resource = Chef::Resource::Subversion.new("my app") + @resource.repository "http://svn.example.org/trunk/" + @resource.destination "/my/deploy/dir" + @resource.revision "12345" + @node = Chef::Node.new + @provider = Chef::Provider::Subversion.new(@node, @resource) + end + + it "converts resource attributes to options for run_command and popen4" do + @provider.run_options.should == {} + @resource.user 'deployninja' + @provider.run_options.should == {:user => "deployninja"} + end + + context "determining the revision of the currently deployed code" do + + before do + @stdout = mock("stdout") + @stderr = mock("stderr") + @exitstatus = mock("exitstatus") + end + + it "sets the revision to nil if there isn't any deployed code yet" do + ::File.should_receive(:exist?).with("/my/deploy/dir").and_return(false) + @provider.find_current_revision.should be_nil + end + + it "determines the current revision if there's a checkout with svn data available" do + example_svn_info = "Path: .\n" + + "URL: http://svn.example.org/trunk/myapp\n" + + "Repository Root: http://svn.example.org\n" + + "Repository UUID: d62ff500-7bbc-012c-85f1-0026b0e37c24\n" + + "Revision: 11739\nNode Kind: directory\n" + + "Schedule: normal\n" + + "Last Changed Author: codeninja\n" + + "Last Changed Rev: 11410\n" + # Last Changed Rev is preferred to Revision + "Last Changed Date: 2009-03-25 06:09:56 -0600 (Wed, 25 Mar 2009)\n\n" + ::File.should_receive(:exist?).with("/my/deploy/dir").and_return(true) + ::File.should_receive(:directory?).with("/my/deploy/dir").and_return(true) + ::Dir.should_receive(:chdir).with("/my/deploy/dir").and_yield + @stdout.stub!(:string).and_return(example_svn_info) + @stderr.stub!(:string).and_return("") + @exitstatus.stub!(:exitstatus).and_return(0) + expected_command = ["svn info", {:cwd=>"/my/deploy/dir"}] + @provider.should_receive(:popen4).with(*expected_command). + and_yield("no-pid", "no-stdin", @stdout,@stderr). + and_return(@exitstatus) + @provider.find_current_revision.should eql("11410") + end + + it "gives nil as the current revision if the deploy dir isn't a SVN working copy" do + example_svn_info = "svn: '/tmp/deploydir' is not a working copy\n" + ::File.should_receive(:exist?).with("/my/deploy/dir").and_return(true) + ::File.should_receive(:directory?).with("/my/deploy/dir").and_return(true) + ::Dir.should_receive(:chdir).with("/my/deploy/dir").and_yield + @stdout.stub!(:string).and_return(example_svn_info) + @stderr.stub!(:string).and_return("") + @exitstatus.stub!(:exitstatus).and_return(1) + @provider.should_receive(:popen4).and_yield("no-pid", "no-stdin", @stdout,@stderr). + and_return(@exitstatus) + @provider.find_current_revision.should be_nil + end + + end + + it "creates the current_resource object and sets its revision to the current deployment's revision" do + @provider.stub!(:find_current_revision).and_return("11410") + @provider.load_current_resource + @provider.current_resource.name.should eql(@resource.name) + @provider.current_resource.revision.should eql("11410") + end + + context "resolving revisions to an integer" do + + before do + @stdout = mock("stdout") + @stderr = mock("stderr") + end + + it "returns the revision number as is if it's already an integer" do + @provider.revision_int.should eql("12345") + end + + it "queries the server and resolves the revision if it's not an integer (i.e. 'HEAD')" do + example_svn_info = "Path: .\n" + + "URL: http://svn.example.org/trunk/myapp\n" + + "Repository Root: http://svn.example.org\n" + + "Repository UUID: d62ff500-7bbc-012c-85f1-0026b0e37c24\n" + + "Revision: 11739\nNode Kind: directory\n" + + "Schedule: normal\n" + + "Last Changed Author: codeninja\n" + + "Last Changed Rev: 11410\n" + # Last Changed Rev is preferred to Revision + "Last Changed Date: 2009-03-25 06:09:56 -0600 (Wed, 25 Mar 2009)\n\n" + exitstatus = mock("exitstatus") + exitstatus.stub!(:exitstatus).and_return(0) + @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). + and_return(exitstatus) + @provider.revision_int.should eql("11410") + end + + it "responds to :revision_slug as an alias for revision_sha" do + @provider.should respond_to(:revision_slug) + end + + end + + it "generates a checkout command with default options" do + @provider.checkout_command.should eql("svn checkout -q -r12345 http://svn.example.org/trunk/ /my/deploy/dir") + end + + it "generates a checkout command with authentication" do + @resource.svn_username "deployNinja" + @resource.svn_password "vanish!" + @provider.checkout_command.should eql("svn checkout -q --username deployNinja --password vanish! " + + "-r12345 http://svn.example.org/trunk/ /my/deploy/dir") + end + + it "generates a checkout command with arbitrary options" do + @resource.svn_arguments "--no-auth-cache" + @provider.checkout_command.should eql("svn checkout --no-auth-cache -q -r12345 "+ + "http://svn.example.org/trunk/ /my/deploy/dir") + end + + it "generates a sync command with default options" do + @provider.sync_command.should eql("svn update -q -r12345 /my/deploy/dir") + end + + it "generates an export command with default options" do + @provider.export_command.should eql("svn export -q -r12345 http://svn.example.org/trunk/ /my/deploy/dir") + end + + it "runs the checkout command for action_checkout" do + expected_cmd = "svn checkout -q -r12345 http://svn.example.org/trunk/ /my/deploy/dir" + @provider.should_receive(:run_command).with(:command => expected_cmd) + @provider.action_checkout + end + + it "does a checkout for action_sync if there's no deploy dir" do + ::File.should_receive(:exist?).with("/my/deploy/dir/.svn").and_return(false) + @provider.should_receive(:action_checkout) + @provider.action_sync + end + + it "does a checkout for action_sync if the deploy dir exists but is empty" do + ::File.should_receive(:exist?).with("/my/deploy/dir/.svn").and_return(true) + ::Dir.should_receive(:entries).with("/my/deploy/dir").and_return(['.','..']) + @provider.should_receive(:action_checkout) + @provider.action_sync + end + + it "runs the sync_command on action_sync if the deploy dir exists and isn't empty" do + ::File.should_receive(:exist?).with("/my/deploy/dir/.svn").and_return(true) + ::Dir.should_receive(:entries).with("/my/deploy/dir").and_return(['.','..','the','app','exists']) + expected_cmd = "svn update -q -r12345 /my/deploy/dir" + @provider.should_receive(:run_command).with(:command => expected_cmd) + @provider.action_sync + end + + it "runs the export_command on action_export" do + expected_cmd = "svn export -q -r12345 http://svn.example.org/trunk/ /my/deploy/dir" + @provider.should_receive(:run_command).with(:command => expected_cmd) + @provider.action_export + end + +end diff --git a/chef/spec/unit/provider_spec.rb b/chef/spec/unit/provider_spec.rb index 49e3b9b6c2..19c444c1ab 100644 --- a/chef/spec/unit/provider_spec.rb +++ b/chef/spec/unit/provider_spec.rb @@ -21,6 +21,7 @@ require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper")) describe Chef::Provider do before(:each) do @resource = Chef::Resource.new("funk") + @resource.cookbook_name = "a_delicious_pie" @node = Chef::Node.new @node.name "latte" @provider = Chef::Provider.new(@node, @resource) @@ -45,4 +46,17 @@ describe Chef::Provider do it "should return true for action_nothing" do @provider.action_nothing.should eql(true) end + + it "sets @cookbook_name to the cookbook name given by @new_resource" do + @provider.instance_variable_get(:@cookbook_name).should == "a_delicious_pie" + end + + it "evals embedded recipes with a pristine resource collection" do + @provider.instance_variable_set(:@collection, "bouncyCastle") + temporary_collection = nil + snitch = lambda {temporary_collection = @collection} + @provider.send(:recipe_eval, &snitch) + temporary_collection.should be_an_instance_of(Chef::ResourceCollection) + @provider.instance_variable_get(:@collection).should == "bouncyCastle" + end end
\ No newline at end of file diff --git a/chef/spec/unit/resource/cron_spec.rb b/chef/spec/unit/resource/cron_spec.rb index 28152342b1..8c308754da 100644 --- a/chef/spec/unit/resource/cron_spec.rb +++ b/chef/spec/unit/resource/cron_spec.rb @@ -78,6 +78,26 @@ describe Chef::Resource::Cron do @resource.weekday.should eql("2") end + it "should allow you to specify the mailto variable" do + @resource.mailto "test@example.com" + @resource.mailto.should eql("test@example.com") + end + + it "should allow you to specify the path" do + @resource.path "/usr/bin:/usr/sbin" + @resource.path.should eql("/usr/bin:/usr/sbin") + end + + it "should allow you to specify the home directory" do + @resource.home "/root" + @resource.home.should eql("/root") + end + + it "should allow you to specify the shell to run the command with" do + @resource.shell "/bin/zsh" + @resource.shell.should eql("/bin/zsh") + end + it "should allow * for all time and date values" do [ "minute", "hour", "day", "month", "weekday" ].each do |x| @resource.send(x, "*").should eql("*") diff --git a/chef/spec/unit/resource/deploy_revision_spec.rb b/chef/spec/unit/resource/deploy_revision_spec.rb new file mode 100644 index 0000000000..4c40bccca5 --- /dev/null +++ b/chef/spec/unit/resource/deploy_revision_spec.rb @@ -0,0 +1,37 @@ +# +# Author:: Daniel DeLeo (<dan@kallistec.com>) +# Copyright:: Copyright (c) 2009 Daniel DeLeo +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper")) + +describe Chef::Resource::DeployRevision do + + it "defaults to the revision deploy provider" do + @resource = Chef::Resource::DeployRevision.new("deploy _this_!") + @resource.provider.should == Chef::Provider::Deploy::Revision + end + +end + +describe Chef::Resource::DeployBranch do + + it "defaults to the revision deploy provider" do + @resource = Chef::Resource::DeployBranch.new("deploy _this_!") + @resource.provider.should == Chef::Provider::Deploy::Revision + end + +end
\ No newline at end of file diff --git a/chef/spec/unit/resource/deploy_spec.rb b/chef/spec/unit/resource/deploy_spec.rb new file mode 100644 index 0000000000..96f60ca8dc --- /dev/null +++ b/chef/spec/unit/resource/deploy_spec.rb @@ -0,0 +1,199 @@ +# +# Author:: Daniel DeLeo (<dan@kallistec.com>) +# Copyright:: Copyright (c) 2008 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper")) + +describe Chef::Resource::Deploy do + + class << self + def resource_has_a_string_attribute(attr_name) + it "has a String attribute for #{attr_name.to_s}" do + @resource.send(attr_name, "this is a string") + @resource.send(attr_name).should eql("this is a string") + lambda {@resource.send(attr_name, 8675309)}.should raise_error(ArgumentError) + end + end + + def resource_has_a_boolean_attribute(attr_name, opts={:defaults_to=>false}) + it "has a Boolean attribute for #{attr_name.to_s}" do + @resource.send(attr_name).should eql(opts[:defaults_to]) + @resource.send(attr_name, !opts[:defaults_to]) + @resource.send(attr_name).should eql( !opts[:defaults_to] ) + end + end + + def resource_has_a_callback_attribute(attr_name) + it "has a Callback attribute #{attr_name}" do + callback_block = lambda { :noop } + lambda {@resource.send(attr_name, &callback_block)}.should_not raise_error + @resource.send(attr_name).should == callback_block + callback_file = "path/to/callback.rb" + lambda {@resource.send(attr_name, callback_file)}.should_not raise_error + @resource.send(attr_name).should == callback_file + lambda {@resource.send(attr_name, :this_is_fail)}.should raise_error(ArgumentError) + end + end + end + + before do + @resource = Chef::Resource::Deploy.new("/my/deploy/dir") + end + + resource_has_a_string_attribute(:repo) + resource_has_a_string_attribute(:deploy_to) + resource_has_a_string_attribute(:role) + resource_has_a_string_attribute(:restart_command) + resource_has_a_string_attribute(:migration_command) + resource_has_a_string_attribute(:user) + resource_has_a_string_attribute(:group) + resource_has_a_string_attribute(:repository_cache) + resource_has_a_string_attribute(:copy_exclude) + resource_has_a_string_attribute(:revision) + resource_has_a_string_attribute(:remote) + resource_has_a_string_attribute(:git_ssh_wrapper) + resource_has_a_string_attribute(:svn_username) + resource_has_a_string_attribute(:svn_password) + resource_has_a_string_attribute(:svn_arguments) + + resource_has_a_boolean_attribute(:migrate, :defaults_to=>false) + resource_has_a_boolean_attribute(:enable_submodules, :defaults_to=>false) + resource_has_a_boolean_attribute(:shallow_clone, :defaults_to=>false) + resource_has_a_boolean_attribute(:force_deploy, :defaults_to=>false) + + it "uses the first argument as the deploy directory" do + @resource.deploy_to.should eql("/my/deploy/dir") + end + + # For git, any revision, branch, tag, whatever is resolved to a SHA1 ref. + # For svn, the branch is included in the repo URL. + # Therefore, revision and branch ARE NOT SEPARATE THINGS + it "aliases #revision as #branch" do + @resource.branch "stable" + @resource.revision.should eql("stable") + end + + it "takes the SCM resource to use as a constant, and defaults to git" do + @resource.scm_provider.should eql(Chef::Provider::Git) + @resource.scm_provider Chef::Provider::Subversion + @resource.scm_provider.should eql(Chef::Provider::Subversion) + end + + it "takes arbitrary environment variables in a hash" do + @resource.environment "RAILS_ENV" => "production" + @resource.environment.should == {"RAILS_ENV" => "production"} + end + + it "takes string arguments to environment for backwards compat, setting RAILS_ENV, RACK_ENV, and MERB_ENV" do + @resource.environment "production" + @resource.environment.should == {"RAILS_ENV"=>"production", "RACK_ENV"=>"production","MERB_ENV"=>"production"} + end + + it "sets destination to $deploy_to/shared/$repository_cache" do + @resource.destination.should eql("/my/deploy/dir/shared/cached-copy/") + end + + it "sets shared_path to $deploy_to/shared" do + @resource.shared_path.should eql("/my/deploy/dir/shared") + end + + it "sets current_path to $deploy_to/current" do + @resource.current_path.should eql("/my/deploy/dir/current") + end + + it "gets the current_path correct even if the shared_path is set (regression test)" do + @resource.shared_path + @resource.current_path.should eql("/my/deploy/dir/current") + end + + it "gives #depth as 5 if shallow clone is true, nil otherwise" do + @resource.depth.should be_nil + @resource.shallow_clone true + @resource.depth.should eql("5") + end + + it "aliases repo as repository" do + @resource.repository "git@github.com/opcode/cookbooks.git" + @resource.repo.should eql("git@github.com/opcode/cookbooks.git") + end + + it "aliases git_ssh_wrapper as ssh_wrapper" do + @resource.ssh_wrapper "git_my_repo.sh" + @resource.git_ssh_wrapper.should eql("git_my_repo.sh") + end + + it "has an Array attribute purge_before_symlink, default: log, tmp/pids, public/system" do + @resource.purge_before_symlink.should == %w{ log tmp/pids public/system } + @resource.purge_before_symlink %w{foo bar baz} + @resource.purge_before_symlink.should == %w{foo bar baz} + end + + it "has an Array attribute create_dirs_before_symlink, default: tmp, public, config" do + @resource.create_dirs_before_symlink.should == %w{tmp public config} + @resource.create_dirs_before_symlink %w{foo bar baz} + @resource.create_dirs_before_symlink.should == %w{foo bar baz} + end + + it 'has a Hash attribute symlinks, default: {"system" => "public/system", "pids" => "tmp/pids", "log" => "log"}' do + default = { "system" => "public/system", "pids" => "tmp/pids", "log" => "log"} + @resource.symlinks.should == default + @resource.symlinks "foo" => "bar/baz" + @resource.symlinks.should == {"foo" => "bar/baz"} + end + + it 'has a Hash attribute symlink_before_migrate, default "config/database.yml" => "config/database.yml"' do + @resource.symlink_before_migrate.should == {"config/database.yml" => "config/database.yml"} + @resource.symlink_before_migrate "wtf?" => "wtf is going on" + @resource.symlink_before_migrate.should == {"wtf?" => "wtf is going on"} + end + + resource_has_a_callback_attribute :before_migrate + resource_has_a_callback_attribute :before_symlink + resource_has_a_callback_attribute :before_restart + resource_has_a_callback_attribute :after_restart + + it "aliases restart_command as restart" do + @resource.restart "foobaz" + @resource.restart_command.should == "foobaz" + end + + it "takes a block for the restart parameter" do + restart_like_this = lambda {p :noop} + @resource.restart(&restart_like_this) + @resource.restart.should == restart_like_this + end + + it "defaults to using the Deploy::Timestamped provider" do + @resource.provider.should == Chef::Provider::Deploy::Timestamped + end + + it "allows providers to be set with a full class name" do + @resource.provider Chef::Provider::Deploy::Timestamped + @resource.provider.should == Chef::Provider::Deploy::Timestamped + end + + it "allows deploy providers to be set via symbol" do + @resource.provider :revision + @resource.provider.should == Chef::Provider::Deploy::Revision + end + + it "allows deploy providers to be set via string" do + @resource.provider "revision" + @resource.provider.should == Chef::Provider::Deploy::Revision + end + +end diff --git a/chef/spec/unit/resource/git_spec.rb b/chef/spec/unit/resource/git_spec.rb new file mode 100644 index 0000000000..2382fb0d8c --- /dev/null +++ b/chef/spec/unit/resource/git_spec.rb @@ -0,0 +1,46 @@ +# +# Author:: Daniel DeLeo (<dan@kallistec.com>) +# Copyright:: Copyright (c) 2008 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper")) + +describe Chef::Resource::Git do + + before(:each) do + @git = Chef::Resource::Git.new("my awesome webapp") + end + + it "is a kind of Scm Resource" do + @git.should be_a_kind_of(Chef::Resource::Scm) + @git.should be_an_instance_of(Chef::Resource::Git) + end + + it "uses the git provider" do + @git.provider.should eql(Chef::Provider::Git) + end + + it "uses aliases revision as branch" do + @git.branch "HEAD" + @git.revision.should eql("HEAD") + end + + it "aliases revision as reference" do + @git.reference "v1.0 tag" + @git.revision.should eql("v1.0 tag") + end + +end diff --git a/chef/spec/unit/resource/group_spec.rb b/chef/spec/unit/resource/group_spec.rb index d110f5a45f..0a7c87ee50 100644 --- a/chef/spec/unit/resource/group_spec.rb +++ b/chef/spec/unit/resource/group_spec.rb @@ -43,6 +43,10 @@ describe Chef::Resource::Group, "initialize" do it "should default members to an empty array" do @resource.members.should eql([]) end + + it "should alias users to members, also an empty array" do + @resource.users.should eql([]) + end it "should set action to :create" do @resource.action.should eql(:create) @@ -90,18 +94,20 @@ describe Chef::Resource::Group, "members" do @resource = Chef::Resource::Group.new("admin") end - it "should allow and convert a string" do - @resource.members "aj" - @resource.members.should eql(["aj"]) - end + [ :users, :members].each do |method| + it "(#{method}) should allow and convert a string" do + @resource.send(method, "aj") + @resource.send(method).should eql(["aj"]) + end - it "should allow an array" do - @resource.members [ "aj", "adam" ] - @resource.members.should eql( ["aj", "adam"] ) - end + it "(#{method}) should allow an array" do + @resource.send(method, [ "aj", "adam" ]) + @resource.send(method).should eql( ["aj", "adam"] ) + end - it "should not allow a hash" do - lambda { @resource.send(:members, { :aj => "is freakin awesome" }) }.should raise_error(ArgumentError) + it "(#{method}) should not allow a hash" do + lambda { @resource.send(method, { :aj => "is freakin awesome" }) }.should raise_error(ArgumentError) + end end end diff --git a/chef/spec/unit/resource/scm_spec.rb b/chef/spec/unit/resource/scm_spec.rb new file mode 100644 index 0000000000..6af37e5f78 --- /dev/null +++ b/chef/spec/unit/resource/scm_spec.rb @@ -0,0 +1,122 @@ +# +# Author:: Daniel DeLeo (<dan@kallistec.com>) +# Copyright:: Copyright (c) 2008 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper")) + +describe Chef::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) + @resource.allowed_actions.should include(:sync) + @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 "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 "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 diff --git a/chef/spec/unit/resource/subversion_spec.rb b/chef/spec/unit/resource/subversion_spec.rb new file mode 100644 index 0000000000..ba00ae5b1a --- /dev/null +++ b/chef/spec/unit/resource/subversion_spec.rb @@ -0,0 +1,36 @@ +# +# Author:: Daniel DeLeo (<dan@kallistec.com>) +# Copyright:: Copyright (c) 2008 Opscode, Inc. +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper")) + +describe Chef::Resource::Subversion do + + before do + @svn = Chef::Resource::Subversion.new("ohai, svn project!") + end + + it "is a subclass of Resource::Scm" do + @svn.should be_an_instance_of(Chef::Resource::Subversion) + @svn.should be_a_kind_of(Chef::Resource::Scm) + end + + it "uses the subversion provider" do + @svn.provider.should eql(Chef::Provider::Subversion) + end + +end
\ No newline at end of file diff --git a/chef/spec/unit/resource/timestamped_deploy_spec.rb b/chef/spec/unit/resource/timestamped_deploy_spec.rb new file mode 100644 index 0000000000..f09ca861f9 --- /dev/null +++ b/chef/spec/unit/resource/timestamped_deploy_spec.rb @@ -0,0 +1,28 @@ +# +# Author:: Daniel DeLeo (<dan@kallistec.com>) +# Copyright:: Copyright (c) 2009 Daniel DeLeo +# License:: Apache License, Version 2.0 +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +require File.expand_path(File.join(File.dirname(__FILE__), "..", "..", "spec_helper")) + +describe Chef::Resource::TimestampedDeploy do + + it "defaults to the TimestampedDeploy provider" do + @resource = Chef::Resource::TimestampedDeploy.new("stuff") + @resource.provider.should == Chef::Provider::Deploy::Timestamped + end + +end
\ No newline at end of file diff --git a/chef/spec/unit/resource_collection_spec.rb b/chef/spec/unit/resource_collection_spec.rb index bfb445bd3e..55ebe5c087 100644 --- a/chef/spec/unit/resource_collection_spec.rb +++ b/chef/spec/unit/resource_collection_spec.rb @@ -1,6 +1,7 @@ # # Author:: Adam Jacob (<adam@opscode.com>) -# Copyright:: Copyright (c) 2008 Opscode, Inc. +# Author:: Christopher Walters (<cw@opscode.com>) +# Copyright:: Copyright (c) 2008, 2009 Opscode, Inc. # License:: Apache License, Version 2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -56,6 +57,37 @@ describe Chef::ResourceCollection do end end + describe "insert" do + it "should accept only Chef::Resources" do + lambda { @rc.insert(@resource) }.should_not raise_error + lambda { @rc.insert("string") }.should raise_error + end + + it "should append resources to the end of the collection when not executing a run" do + zmr = Chef::Resource::ZenMaster.new("there is no spoon") + @rc.insert(@resource) + @rc.insert(zmr) + @rc[0].should eql(@resource) + @rc[1].should eql(zmr) + end + + it "should insert resources to the middle of the collection if called while executing a run" do + resource_to_inject = Chef::Resource::ZenMaster.new("there is no spoon") + zmr = Chef::Resource::ZenMaster.new("morpheus") + dummy = Chef::Resource::ZenMaster.new("keanu reeves") + @rc.insert(zmr) + @rc.insert(dummy) + + @rc.execute_each_resource do |resource| + @rc.insert(resource_to_inject) if resource == zmr + end + + @rc[0].should eql(zmr) + @rc[1].should eql(resource_to_inject) + @rc[2].should eql(dummy) + end + end + describe "each" do it "should allow you to iterate over every resource in the collection" do load_up_resources @@ -204,4 +236,4 @@ describe Chef::ResourceCollection do @rc << Chef::Resource::File.new("something") end -end
\ No newline at end of file +end diff --git a/chef/spec/unit/resource_spec.rb b/chef/spec/unit/resource_spec.rb index 449be098c8..2978ebf168 100644 --- a/chef/spec/unit/resource_spec.rb +++ b/chef/spec/unit/resource_spec.rb @@ -18,6 +18,10 @@ require File.expand_path(File.join(File.dirname(__FILE__), "..", "spec_helper")) +class ResourceTestHarness < Chef::Resource + provider_base Chef::Provider::Package +end + describe Chef::Resource do before(:each) do @resource = Chef::Resource.new("funk") @@ -164,6 +168,16 @@ describe Chef::Resource do end end + describe "to_hash" do + it "should convert to a hash" do + hash = @resource.to_hash + hash.keys.should include( :only_if, :allowed_actions, :params, :provider, + :updated, :before, :not_if, :supports, :node, + :actions, :noop, :ignore_failure, :name, :source_line, :action) + hash[:name].should eql("funk") + end + end + describe "self.json_create" do it "should deserialize itself from json" do json = @resource.to_json @@ -201,4 +215,16 @@ describe Chef::Resource do end end + describe "setting the base provider class for the resource" do + + it "defaults to Chef::Provider for the base class" do + Chef::Resource.provider_base.should == Chef::Provider + end + + it "allows the base provider to be overriden by a " do + ResourceTestHarness.provider_base.should == Chef::Provider::Package + end + + end + end
\ No newline at end of file diff --git a/chef/spec/unit/runner_spec.rb b/chef/spec/unit/runner_spec.rb index fca8af4542..c1400af22f 100644 --- a/chef/spec/unit/runner_spec.rb +++ b/chef/spec/unit/runner_spec.rb @@ -1,4 +1,4 @@ -# + # Author:: Adam Jacob (<adam@opscode.com>) # Copyright:: Copyright (c) 2008 Opscode, Inc. # License:: Apache License, Version 2.0 @@ -55,7 +55,7 @@ describe Chef::Runner do end it "should pass each resource in the collection to a provider" do - @collection.should_receive(:each).once + @collection.should_receive(:execute_each_resource).once @runner.converge end @@ -129,6 +129,21 @@ describe Chef::Runner do @runner.converge end + it "should follow a chain of actions" do + Chef::Platform.should_receive(:find_provider_for_node).exactly(5).times.and_return(Chef::Provider::SnakeOil) + @collection << Chef::Resource::Cat.new("peanut", @collection) + @collection[1].notifies :buy, @collection[0], :immediately + @collection << Chef::Resource::Cat.new("snuffles", @collection) + @collection[2].notifies :purr, @collection[1], :immediately + @collection[2].updated = true + provider = Chef::Provider::SnakeOil.new(@node, @collection[0]) + p1 = Chef::Provider::SnakeOil.new(@node, @collection[1]) + p2 = Chef::Provider::SnakeOil.new(@node, @collection[2]) + Chef::Provider::SnakeOil.should_receive(:new).exactly(5).times.and_return(provider, p1, p2, p1, provider) + provider.should_receive(:action_buy).once.and_return(true) + @runner.converge + end + it "should execute delayed actions on changed resources" do Chef::Platform.should_receive(:find_provider_for_node).exactly(3).times.and_return(Chef::Provider::SnakeOil) provider = Chef::Provider::SnakeOil.new(@node, @collection[0]) @@ -153,4 +168,4 @@ describe Chef::Runner do @runner.converge end -end
\ No newline at end of file +end diff --git a/cucumber.yml b/cucumber.yml index 13f05ec452..e83846b0bb 100644 --- a/cucumber.yml +++ b/cucumber.yml @@ -39,3 +39,9 @@ provider_package_macports: --tags @macports --format pretty -r features/steps -r language: --tags @language --format pretty -r features/steps -r features/support features recipe_inclusion: --tags recipe_inclusion --format pretty -r features/steps -r features/support features cookbooks: --tags @cookbooks --format pretty -r features/steps -r features/support features +provider_remote_file: --tags provider,remote_file --format pretty -r features/steps -r features/support features +provider_git: --tags provider,git --format pretty -r features/steps -r features/support features +provider_template: --tags template --format pretty -r features/steps -r features/support features +provider_package_macports: --tags macports --format pretty -r features/steps -r features/support features +lwrp: --tags lwrp --format pretty -r features/steps -r features/support features + diff --git a/features/cookbooks/lightweight_resources_and_providers.feature b/features/cookbooks/lightweight_resources_and_providers.feature new file mode 100644 index 0000000000..1c567906fd --- /dev/null +++ b/features/cookbooks/lightweight_resources_and_providers.feature @@ -0,0 +1,55 @@ +@lwrp @cookbooks +Feature: Light-weight resources and providers + + @solo + Scenario Outline: Chef solo handles light-weight resources and providers + Given a local cookbook repository + When I run chef-solo with the 'lwrp::<recipe>' recipe + Then the run should exit '0' + And 'stdout' should have '<message>' + + Examples: + | recipe | message | + | default_everything | Default everything | + | non_default_provider | Non-default provider | + | non_default_resource | Non-default resource | + | overridden_resource_initialize | Overridden initialize | + | overridden_provider_load_current_resource | Overridden load_current_resource | + | provider_is_a_string | Provider is a string | + | provider_is_a_symbol | Provider is a symbol | + | provider_is_a_class | Provider is a class | + + @solo + Scenario: Chef solo properly handles providers that invoke resources in their action definitions + Given a local cookbook repository + When I run chef-solo with the 'lwrp::provider_invokes_resource' recipe + Then the run should exit '0' + And a file named 'lwrp_touch_file.txt' should exist + + @client @api + Scenario Outline: Chef-client handles light-weight resources and providers + Given a validated node + And it includes the recipe 'lwrp::<recipe>' + When I run the chef-client + Then the run should exit '0' + And 'stdout' should have '<message>' + + Examples: + | recipe | message | + | default_everything | Default everything | + | non_default_provider | Non-default provider | + | non_default_resource | Non-default resource | + | overridden_resource_initialize | Overridden initialize | + | overridden_provider_load_current_resource | Overridden load_current_resource | + | provider_is_a_string | Provider is a string | + | provider_is_a_symbol | Provider is a symbol | + | provider_is_a_class | Provider is a class | + + @client @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' + When I run the chef-client + Then the run should exit '0' + And a file named 'lwrp_touch_file.txt' should exist + diff --git a/features/data/cookbooks/deploy/recipes/callbacks.rb b/features/data/cookbooks/deploy/recipes/callbacks.rb new file mode 100644 index 0000000000..4f1d0f9a66 --- /dev/null +++ b/features/data/cookbooks/deploy/recipes/callbacks.rb @@ -0,0 +1,78 @@ +# +# Cookbook Name:: deploy +# Recipe:: default +# +# Copyright 2009, Opscode +# +# 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. +# + +%w{ log pids system config sqlite deploy}.each do |dir| + directory "#{node[:tmpdir]}/deploy/shared/#{dir}" do + recursive true + mode "0775" + end +end + + +template "#{node[:tmpdir]}/deploy/shared/config/database.yml" do + source "database.yml.erb" + mode "0664" +end + +template "#{node[:tmpdir]}/deploy/shared/config/app_config.yml" do + source "app_config.yml.erb" + mode "0664" +end + +template "#{node[:tmpdir]}/deploy/shared/deploy/before_migrate.rb" do + source "sneaky_before_migrate_hook.rb.erb" + mode "0644" +end + +template "#{node[:tmpdir]}/deploy/shared/deploy/before_symlink.rb" do + source "sneaky_before_symlink_hook.rb.erb" + mode "0644" +end + +template "#{node[:tmpdir]}/deploy/shared/deploy/before_restart.rb" do + source "sneaky_before_restart_hook.rb.erb" + mode "0644" +end + +template "#{node[:tmpdir]}/deploy/shared/deploy/after_restart.rb" do + source "sneaky_after_restart_hook.rb.erb" + mode "0644" +end + +file "#{node[:tmpdir]}/deploy/shared/sqlite/production.sqlite3" do + mode "0664" +end + +deploy "#{node[:tmpdir]}/deploy" do + repo "#{node[:tmpdir]}/gitrepo/typo/" + environment "RAILS_ENV" => "production" + revision "HEAD" + action :deploy + migration_command "rake db:migrate --trace" + migrate true + restart_command "touch tmp/restart.txt" + create_dirs_before_symlink %w{tmp public config deploy} + symlink_before_migrate "config/database.yml" => "config/database.yml" + + symlinks "system" => "public/system", "pids" => "tmp/pids", "log" => "log", + "deploy/before_migrate.rb" => "deploy/before_migrate.rb", + "deploy/before_symlink.rb" => "deploy/before_symlink.rb", + "deploy/before_restart.rb" => "deploy/before_restart.rb", + "deploy/after_restart.rb" => "deploy/after_restart.rb" +end
\ No newline at end of file diff --git a/features/data/cookbooks/deploy/recipes/default.rb b/features/data/cookbooks/deploy/recipes/default.rb new file mode 100644 index 0000000000..6713cf90ef --- /dev/null +++ b/features/data/cookbooks/deploy/recipes/default.rb @@ -0,0 +1,51 @@ +# +# Cookbook Name:: deploy +# Recipe:: default +# +# Copyright 2009, Opscode +# +# 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. +# + +%w{ log pids system config sqlite}.each do |dir| + directory "#{node[:tmpdir]}/deploy/shared/#{dir}" do + recursive true + mode "0775" + end +end + + +template "#{node[:tmpdir]}/deploy/shared/config/database.yml" do + source "database.yml.erb" + mode "0664" +end + +template "#{node[:tmpdir]}/deploy/shared/config/app_config.yml" do + source "app_config.yml.erb" + mode "0664" +end + +file "#{node[:tmpdir]}/deploy/shared/sqlite/production.sqlite3" do + mode "0664" +end + +deploy "#{node[:tmpdir]}/deploy" do + repo "#{node[:tmpdir]}/gitrepo/typo/" + environment "RAILS_ENV" => "production" + revision "HEAD" + action :deploy + migration_command "rake db:migrate --trace" + migrate true + restart_command "touch tmp/restart.txt" +end + diff --git a/features/data/cookbooks/deploy/recipes/embedded_recipe_callbacks.rb b/features/data/cookbooks/deploy/recipes/embedded_recipe_callbacks.rb new file mode 100644 index 0000000000..a57884fae9 --- /dev/null +++ b/features/data/cookbooks/deploy/recipes/embedded_recipe_callbacks.rb @@ -0,0 +1,70 @@ +# +# Cookbook Name:: deploy +# Recipe:: embedded_recipe_callbacks +# +# Copyright 2009, Daniel DeLeo +# +# 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. +# + +%w{ log pids system config sqlite deploy}.each do |dir| + directory "#{node[:tmpdir]}/deploy/shared/#{dir}" do + recursive true + mode "0775" + end +end + + +template "#{node[:tmpdir]}/deploy/shared/config/database.yml" do + source "database.yml.erb" + mode "0664" +end + +file "#{node[:tmpdir]}/deploy/shared/sqlite/production.sqlite3" do + mode "0664" +end + +timestamped_deploy "#{node[:tmpdir]}/deploy" do + repo "#{node[:tmpdir]}/gitrepo/typo/" + environment "RAILS_ENV" => "production" + revision "HEAD" + action :deploy + migration_command "rake db:migrate --trace" + migrate true + + # Callback awesomeness: + before_migrate do + current_release = release_path + + directory "#{current_release}/deploy" do + mode "0755" + end + + # creates a callback for before_symlink + template "#{current_release}/deploy/before_symlink_callback.rb" do + source "embedded_recipe_before_symlink.rb.erb" + mode "0644" + end + + end + + before_symlink "deploy/before_symlink_callback.rb" + + restart do + current_release = release_path + file "#{release_path}/tmp/restart.txt" do + mode "0644" + end + end + +end
\ No newline at end of file diff --git a/features/data/cookbooks/deploy/recipes/revision_deploy.rb b/features/data/cookbooks/deploy/recipes/revision_deploy.rb new file mode 100644 index 0000000000..5176a47736 --- /dev/null +++ b/features/data/cookbooks/deploy/recipes/revision_deploy.rb @@ -0,0 +1,51 @@ +# +# Cookbook Name:: deploy +# Recipe:: default +# +# Copyright 2009, Opscode +# +# 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. +# + +%w{ log pids system config sqlite}.each do |dir| + directory "#{node[:tmpdir]}/deploy/shared/#{dir}" do + recursive true + mode "0775" + end +end + + +template "#{node[:tmpdir]}/deploy/shared/config/database.yml" do + source "database.yml.erb" + mode "0664" +end + +template "#{node[:tmpdir]}/deploy/shared/config/app_config.yml" do + source "app_config.yml.erb" + mode "0664" +end + +file "#{node[:tmpdir]}/deploy/shared/sqlite/production.sqlite3" do + mode "0664" +end + +deploy_revision "#{node[:tmpdir]}/deploy" do + repo "#{node[:tmpdir]}/gitrepo/typo/" + environment "RAILS_ENV" => "production" + revision "HEAD" + action :deploy + migration_command "rake db:migrate --trace" + migrate true + restart_command "touch tmp/restart.txt" +end + diff --git a/features/data/cookbooks/deploy/recipes/rollback.rb b/features/data/cookbooks/deploy/recipes/rollback.rb new file mode 100644 index 0000000000..34ddbecf40 --- /dev/null +++ b/features/data/cookbooks/deploy/recipes/rollback.rb @@ -0,0 +1,56 @@ +# +# Cookbook Name:: deploy +# Recipe:: rollback +# +# Copyright 2009, Opscode +# +# 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. +# + +%w{ log tmp/pids public/system config sqlite}.each do |dir| + directory "#{node[:tmpdir]}/deploy/shared/#{dir}" do + recursive true + mode "0775" + end +end + +# Use a template instead in real life +file "#{node[:tmpdir]}/deploy/shared/config/database.yml" do + mode "0664" +end + +file "#{node[:tmpdir]}/deploy/shared/sqlite/production.sqlite3" do + mode "0664" +end + +deploy "#{node[:tmpdir]}/deploy" do + repo "file:///#{File.dirname(__FILE__) + "/../../../../../../../"}" + revision "HEAD" + action :deploy + restart_command "touch tmp/restart.txt" +end + +deploy "#{node[:tmpdir]}/deploy" do + repo "file:///#{File.dirname(__FILE__) + "/../../../../../../../"}" + revision "HEAD" + action :deploy + restart_command "touch tmp/restart.txt" +end + +deploy "#{node[:tmpdir]}/deploy" do + repo "file:///#{File.dirname(__FILE__) + "/../../../../../../../"}" + revision "HEAD" + action :rollback + restart_command "touch tmp/restart.txt" +end + diff --git a/features/data/cookbooks/deploy/templates/default/app_config.yml.erb b/features/data/cookbooks/deploy/templates/default/app_config.yml.erb new file mode 100644 index 0000000000..40bb98ee93 --- /dev/null +++ b/features/data/cookbooks/deploy/templates/default/app_config.yml.erb @@ -0,0 +1,23 @@ +--- +default: &default + name: masquerade + host: localhost:3000 + use_ssl: false + time_zone: Berlin + email: info@your.domain.com + session: + session_key: openidserver_session_id + secret: replacethiswithyourowntopsecretsessionsecretsothatnoonewillknowit + mailer: + address: localhost + domain: your.domain.com + from: info@your.domain.com + +development: + <<: *default + +test: + <<: *default + +production: + <<: *default diff --git a/features/data/cookbooks/deploy/templates/default/database.yml.erb b/features/data/cookbooks/deploy/templates/default/database.yml.erb new file mode 100644 index 0000000000..1793d95f55 --- /dev/null +++ b/features/data/cookbooks/deploy/templates/default/database.yml.erb @@ -0,0 +1,18 @@ +development: + adapter: sqlite3 + database: dev.sqlite3 + encoding: utf8 + +test: + adapter: sqlite3 + host: localhost + database: masquerade_test + username: masquerade + password: + encoding: utf8 + +production: + adapter: sqlite3 + host: localhost + database: db/production.sqlite3 + encoding: utf8
\ No newline at end of file diff --git a/features/data/cookbooks/deploy/templates/default/embedded_recipe_before_symlink.rb.erb b/features/data/cookbooks/deploy/templates/default/embedded_recipe_before_symlink.rb.erb new file mode 100644 index 0000000000..8b489f15ac --- /dev/null +++ b/features/data/cookbooks/deploy/templates/default/embedded_recipe_before_symlink.rb.erb @@ -0,0 +1,4 @@ +current_release = release_path +file "#{current_release}/app/before_symlink_was_here.txt" do + mode "0644" +end
\ No newline at end of file diff --git a/features/data/cookbooks/deploy/templates/default/sneaky_after_restart_hook.rb.erb b/features/data/cookbooks/deploy/templates/default/sneaky_after_restart_hook.rb.erb new file mode 100644 index 0000000000..90f93f4183 --- /dev/null +++ b/features/data/cookbooks/deploy/templates/default/sneaky_after_restart_hook.rb.erb @@ -0,0 +1,4 @@ +hook_name = "after_restart" +::File.open(@release_path + "/app/#{hook_name}", "a+") do |fd| + fd.puts( {:hook_name => hook_name, :env => @configuration[:environment]}.to_json ) +end
\ No newline at end of file diff --git a/features/data/cookbooks/deploy/templates/default/sneaky_before_migrate_hook.rb.erb b/features/data/cookbooks/deploy/templates/default/sneaky_before_migrate_hook.rb.erb new file mode 100644 index 0000000000..59a251e48d --- /dev/null +++ b/features/data/cookbooks/deploy/templates/default/sneaky_before_migrate_hook.rb.erb @@ -0,0 +1,2 @@ +# NOOP +# This file isn't symlinked in yet when its callback would be run.
\ No newline at end of file diff --git a/features/data/cookbooks/deploy/templates/default/sneaky_before_restart_hook.rb.erb b/features/data/cookbooks/deploy/templates/default/sneaky_before_restart_hook.rb.erb new file mode 100644 index 0000000000..d514674d4e --- /dev/null +++ b/features/data/cookbooks/deploy/templates/default/sneaky_before_restart_hook.rb.erb @@ -0,0 +1,4 @@ +hook_name = "before_restart" +::File.open(@release_path + "/app/#{hook_name}", "a+") do |fd| + fd.puts( {:hook_name => hook_name, :env => @configuration[:environment]}.to_json ) +end
\ No newline at end of file diff --git a/features/data/cookbooks/deploy/templates/default/sneaky_before_symlink_hook.rb.erb b/features/data/cookbooks/deploy/templates/default/sneaky_before_symlink_hook.rb.erb new file mode 100644 index 0000000000..de5fd98645 --- /dev/null +++ b/features/data/cookbooks/deploy/templates/default/sneaky_before_symlink_hook.rb.erb @@ -0,0 +1,2 @@ +# NOOP +# This file isn't symlinked to the correct path when its callback fires
\ No newline at end of file diff --git a/features/data/cookbooks/lwrp/providers/default.rb b/features/data/cookbooks/lwrp/providers/default.rb new file mode 100644 index 0000000000..f66e2914f6 --- /dev/null +++ b/features/data/cookbooks/lwrp/providers/default.rb @@ -0,0 +1,9 @@ +action :print_message do + puts new_resource.message +end + +action :touch_file do + file "#{node[:tmpdir]}/#{new_resource.filename}" do + action :create + end +end diff --git a/features/data/cookbooks/lwrp/providers/lwp_non_default.rb b/features/data/cookbooks/lwrp/providers/lwp_non_default.rb new file mode 100644 index 0000000000..f51dcae369 --- /dev/null +++ b/features/data/cookbooks/lwrp/providers/lwp_non_default.rb @@ -0,0 +1,3 @@ +action :print_message do + puts new_resource.message +end diff --git a/features/data/cookbooks/lwrp/providers/lwp_overridden_load_current_resource.rb b/features/data/cookbooks/lwrp/providers/lwp_overridden_load_current_resource.rb new file mode 100644 index 0000000000..58689150ee --- /dev/null +++ b/features/data/cookbooks/lwrp/providers/lwp_overridden_load_current_resource.rb @@ -0,0 +1,8 @@ +def load_current_resource + puts "Overridden load_current_resource" +end + +action :print_message do + puts new_resource.message +end + diff --git a/features/data/cookbooks/lwrp/recipes/default_everything.rb b/features/data/cookbooks/lwrp/recipes/default_everything.rb new file mode 100644 index 0000000000..9d00aeb744 --- /dev/null +++ b/features/data/cookbooks/lwrp/recipes/default_everything.rb @@ -0,0 +1,6 @@ +lwrp :default do + message "Default everything" + action :print_message + + provider Chef::Provider::Lwrp +end diff --git a/features/data/cookbooks/lwrp/recipes/non_default_provider.rb b/features/data/cookbooks/lwrp/recipes/non_default_provider.rb new file mode 100644 index 0000000000..019f3aa9ab --- /dev/null +++ b/features/data/cookbooks/lwrp/recipes/non_default_provider.rb @@ -0,0 +1,6 @@ +lwrp :non_default_provider do + message "Non-default provider" + action :print_message + + provider :lwrp_lwp_non_default +end diff --git a/features/data/cookbooks/lwrp/recipes/non_default_resource.rb b/features/data/cookbooks/lwrp/recipes/non_default_resource.rb new file mode 100644 index 0000000000..8df71ca218 --- /dev/null +++ b/features/data/cookbooks/lwrp/recipes/non_default_resource.rb @@ -0,0 +1,6 @@ +lwrp_lwr_non_default :non_default_lwr do + message "Non-default resource" + action :print_message + + provider Chef::Provider::Lwrp +end diff --git a/features/data/cookbooks/lwrp/recipes/overridden_provider_load_current_resource.rb b/features/data/cookbooks/lwrp/recipes/overridden_provider_load_current_resource.rb new file mode 100644 index 0000000000..0b525aa541 --- /dev/null +++ b/features/data/cookbooks/lwrp/recipes/overridden_provider_load_current_resource.rb @@ -0,0 +1,6 @@ +lwrp :overridden_provider_load_current_resource do + message "meep meep" + action :print_message + + provider Chef::Provider::LwrpLwpOverriddenLoadCurrentResource +end diff --git a/features/data/cookbooks/lwrp/recipes/overridden_resource_initialize.rb b/features/data/cookbooks/lwrp/recipes/overridden_resource_initialize.rb new file mode 100644 index 0000000000..6982ce35fa --- /dev/null +++ b/features/data/cookbooks/lwrp/recipes/overridden_resource_initialize.rb @@ -0,0 +1,6 @@ +lwrp_lwr_overridden_initialize :overridden_resource_initialize do + message "meep meep" + action :print_message + + provider Chef::Provider::Lwrp +end diff --git a/features/data/cookbooks/lwrp/recipes/provider_invokes_resource.rb b/features/data/cookbooks/lwrp/recipes/provider_invokes_resource.rb new file mode 100644 index 0000000000..c2e94aa064 --- /dev/null +++ b/features/data/cookbooks/lwrp/recipes/provider_invokes_resource.rb @@ -0,0 +1,6 @@ +lwrp :lwrp_provider_invokes_resource do + filename "lwrp_touch_file.txt" + action :touch_file + + provider :lwrp +end diff --git a/features/data/cookbooks/lwrp/recipes/provider_is_a_class.rb b/features/data/cookbooks/lwrp/recipes/provider_is_a_class.rb new file mode 100644 index 0000000000..efdda31b0e --- /dev/null +++ b/features/data/cookbooks/lwrp/recipes/provider_is_a_class.rb @@ -0,0 +1,6 @@ +lwrp :provider_is_a_class do + message "Provider is a class" + action :print_message + + provider Chef::Provider::Lwrp +end diff --git a/features/data/cookbooks/lwrp/recipes/provider_is_a_string.rb b/features/data/cookbooks/lwrp/recipes/provider_is_a_string.rb new file mode 100644 index 0000000000..78f333bcd4 --- /dev/null +++ b/features/data/cookbooks/lwrp/recipes/provider_is_a_string.rb @@ -0,0 +1,6 @@ +lwrp :provider_is_a_string do + message "Provider is a string" + action :print_message + + provider "lwrp" +end diff --git a/features/data/cookbooks/lwrp/recipes/provider_is_a_symbol.rb b/features/data/cookbooks/lwrp/recipes/provider_is_a_symbol.rb new file mode 100644 index 0000000000..2ca9963fd5 --- /dev/null +++ b/features/data/cookbooks/lwrp/recipes/provider_is_a_symbol.rb @@ -0,0 +1,6 @@ +lwrp :provider_is_a_symbol do + message "Provider is a symbol" + action :print_message + + provider :lwrp +end diff --git a/features/data/cookbooks/lwrp/resources/default.rb b/features/data/cookbooks/lwrp/resources/default.rb new file mode 100644 index 0000000000..00be531184 --- /dev/null +++ b/features/data/cookbooks/lwrp/resources/default.rb @@ -0,0 +1,4 @@ +actions :print_message, :touch_file + +attribute :message, :kind_of => String +attribute :filename, :kind_of => String diff --git a/features/data/cookbooks/lwrp/resources/lwr_non_default.rb b/features/data/cookbooks/lwrp/resources/lwr_non_default.rb new file mode 100644 index 0000000000..c29654eba3 --- /dev/null +++ b/features/data/cookbooks/lwrp/resources/lwr_non_default.rb @@ -0,0 +1,3 @@ +actions :print_message + +attribute :message, :kind_of => String diff --git a/features/data/cookbooks/lwrp/resources/lwr_overridden_initialize.rb b/features/data/cookbooks/lwrp/resources/lwr_overridden_initialize.rb new file mode 100644 index 0000000000..b06e21fcba --- /dev/null +++ b/features/data/cookbooks/lwrp/resources/lwr_overridden_initialize.rb @@ -0,0 +1,8 @@ +def initialize(name, collection=nil, node=nil) + super(name, collection, node) + puts "Overridden initialize" +end + +actions :print_message + +attribute :message, :kind_of => String diff --git a/features/data/cookbooks/scm/metadata.rb b/features/data/cookbooks/scm/metadata.rb new file mode 100644 index 0000000000..31aa11370f --- /dev/null +++ b/features/data/cookbooks/scm/metadata.rb @@ -0,0 +1,8 @@ +version "1.0" +maintainer "Ohai the Cat" +maintainer_email "ohai@cat.example.com" +description "SCM tests" +license 'Apache v2.0' +long_description "SCM integration/acceptance test recipes" + +recipe "scm::git", "git awesome" diff --git a/features/data/cookbooks/scm/recipes/git.rb b/features/data/cookbooks/scm/recipes/git.rb new file mode 100644 index 0000000000..3bbd478193 --- /dev/null +++ b/features/data/cookbooks/scm/recipes/git.rb @@ -0,0 +1,25 @@ +# +# Cookbook Name:: scm +# Recipe:: git +# +# Copyright 2009, Opscode +# +# 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. +# +# If the features are not being run from a git clone, you're out of luck. +git "the chef repo" do + repository "file:///#{File.dirname(__FILE__) + "/../../../../../../../"}" + reference "HEAD" + destination "#{node[:tmpdir]}/gitchef" + action :sync +end diff --git a/features/data/typo.bundle b/features/data/typo.bundle Binary files differnew file mode 100644 index 0000000000..ab2e02fe50 --- /dev/null +++ b/features/data/typo.bundle diff --git a/features/provider/deploy/deploy.feature b/features/provider/deploy/deploy.feature new file mode 100644 index 0000000000..25c13db799 --- /dev/null +++ b/features/provider/deploy/deploy.feature @@ -0,0 +1,71 @@ +@provider @git @deploy + +Feature: Deploy + In order to repeatably and reliably deploy web apps from a source repository from the comfort of chef + As an OpsDev + I want to have automated deployments + + Scenario: Deploy an app for the first time + Given a validated node + And it includes the recipe 'deploy' + And I have a clone of typo in the data/tmp dir + When I run the chef-client + Then the run should exit '0' + And a file named 'deploy/shared' should exist + And a file named 'deploy/shared/cached-copy/.git' should exist + And a file named 'deploy/current/app' should exist + And a file named 'deploy/current/config/database.yml' should exist + And a file named 'deploy/current/db/production.sqlite3' should exist + And a file named 'deploy/current/tmp/restart.txt' should exist + + Scenario: Deploy an app again + Given a validated node + And it includes the recipe 'deploy' + And I have a clone of typo in the data/tmp dir + When I run the chef-client + And I run the chef-client again + And there should be 'two' releases + + Scenario: Deploy an app with custom layout attributes and callbacks + Given a validated node + And it includes the recipe 'deploy::callbacks' + And I have a clone of typo in the data/tmp dir + When I run the chef-client + Then the run should exit '0' + And a callback named <callback_file> should exist + | before_migrate.rb | + | before_symlink.rb | + | before_restart.rb | + | after_restart.rb | + And the callback named <callback> should have run + | before_restart.rb | + | after_restart.rb | + + Scenario: Deploy an app with resources inside the callbacks (embedded recipes) + Given a validated node + And it includes the recipe 'deploy::embedded_recipe_callbacks' + And I have a clone of typo in the data/tmp dir + When I run the chef-client + Then the run should exit '0' + And a file named 'deploy/current/app/before_symlink_was_here.txt' should exist + And a file named 'deploy/current/tmp/restart.txt' should exist + + + + Scenario: Rollback an app + Given a validated node + And it includes the recipe 'deploy::rollback' + When I run the chef-client + Then there should be 'one' release + + + Scenario: Deploy an app twice using the idempotent revision deploy strategy + Given a validated node + And it includes the recipe 'deploy::revision_deploy' + And I have a clone of typo in the data/tmp dir + When I run the chef-client + And I run the chef-client at log level 'info' + Then the run should exit '0' + And there should be 'one' release + And the second chef run should have skipped deployment + diff --git a/features/provider/scm/git.feature b/features/provider/scm/git.feature new file mode 100644 index 0000000000..2b25fca0e6 --- /dev/null +++ b/features/provider/scm/git.feature @@ -0,0 +1,17 @@ +@provider @git + +Feature: Git + In order to use files stored in git so I can deploy apps and use edge versions of software + As a Developer + I want to clone and update git repositories + + Scenario: Clone a git repo + Given a validated node + And it includes the recipe 'scm::git' + When I run the chef-client + Then the run should exit '0' + And a file named 'gitchef/.git' should exist + And a file named 'gitchef/chef' should exist + + + diff --git a/features/steps/deploy_steps.rb b/features/steps/deploy_steps.rb new file mode 100644 index 0000000000..7b9e95a3f2 --- /dev/null +++ b/features/steps/deploy_steps.rb @@ -0,0 +1,59 @@ +Given /^I have a clone of typo in the data\/tmp dir$/ do + cmd = "git clone #{datadir}/typo.bundle #{tmpdir}/gitrepo/typo" + `#{cmd}` +end + +Then /^I should hear about it$/ do + puts "==deploy:" + puts `ls #{tmpdir}/deploy/` + puts "==Releases:" + puts `ls #{tmpdir}/deploy/releases/` + puts "==Releases/*/" + puts `ls #{tmpdir}/deploy/releases/*/` + puts "==Releases/*/db" + puts `ls #{tmpdir}/deploy/releases/*/db/` + puts "==Releases/*/config/" + puts `ls #{tmpdir}/deploy/releases/*/config/` + puts "==current:" + puts `ls #{tmpdir}/deploy/current/` + puts "==current/db:" + puts `ls #{tmpdir}/deploy/current/db/` + puts "==current/deploy:" + puts `ls #{tmpdir}/deploy/current/deploy/` + puts "==current/app:" + puts `ls #{tmpdir}/deploy/current/app/` + puts "==current/config:" + puts `ls #{tmpdir}/deploy/current/config/` + puts "==shared/config/app_config.yml" + puts `ls #{tmpdir}/deploy/shared/config/` +end + +Then /^there should be '(.*)' releases?$/ do |n| + numnums = {"one" => 1, "two" => 2, "three" => 3} + n = numnums.has_key?(n) ? numnums[n] : n.to_i + @releases = Dir.glob(tmpdir + "/deploy/releases/*") + @releases.size.should eql(n) +end + +Then /^a callback named <callback_file> should exist$/ do |callback_files| + callback_files.raw.each do |file| + want_file = "deploy/current/deploy/#{file.first}" + Then "a file named '#{want_file}' should exist" + end +end + +Then /^the callback named <callback> should have run$/ do |callback_files| + callback_files.raw.each do |file| + hook_name = file.first.gsub(/\.rb$/, "") + evidence_file = "deploy/current/app/" + hook_name + expected_contents = {"hook_name" => hook_name, "env" => "production"} + actual_contents = JSON.parse(IO.read(File.join(tmpdir, evidence_file))) + expected_contents.should == actual_contents + end +end + +Then /^the second chef run should have skipped deployment$/ do + expected_deploy = "#{tmpdir}/deploy/releases/62c9979f6694612d9659259f8a68d71048ae9a5b" + Then "'stdout' should have 'INFO: Already deployed app at #{expected_deploy}, skipping. Use action :force_deploy to force.'" +end + diff --git a/features/steps/run_client_steps.rb b/features/steps/run_client_steps.rb index 25726f59aa..b0a9a40c6b 100644 --- a/features/steps/run_client_steps.rb +++ b/features/steps/run_client_steps.rb @@ -31,6 +31,10 @@ When /^I run the chef\-client$/ do @status = status end +When /^I run the chef\-client again$/ do + When "I run the chef-client" +end + When /^I run the chef\-client with '(.+)'$/ do |args| @chef_args = args When "I run the chef-client" |