summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG29
-rw-r--r--Rakefile9
-rw-r--r--chef-server-api/app/controllers/application.rb12
-rw-r--r--chef-server-slice/app/controllers/cookbook_segment.rb56
-rw-r--r--chef-server-webui/app/views/cookbooks/show.html.haml57
-rw-r--r--[-rwxr-xr-x]chef-server-webui/public/facebox/closelabel.gifbin979 -> 979 bytes
-rw-r--r--[-rwxr-xr-x]chef-server-webui/public/facebox/loading.gifbin2767 -> 2767 bytes
-rw-r--r--[-rwxr-xr-x]chef-server-webui/public/images/treeBuilderImages/Thumbs.dbbin20480 -> 20480 bytes
-rw-r--r--[-rwxr-xr-x]chef-server-webui/public/images/treeBuilderImages/doc.gifbin993 -> 993 bytes
-rw-r--r--[-rwxr-xr-x]chef-server-webui/public/images/treeBuilderImages/docNode.gifbin147 -> 147 bytes
-rw-r--r--[-rwxr-xr-x]chef-server-webui/public/images/treeBuilderImages/docNodeLast.gifbin142 -> 142 bytes
-rw-r--r--[-rwxr-xr-x]chef-server-webui/public/images/treeBuilderImages/docNodeLastFirst.gifbin107 -> 107 bytes
-rw-r--r--[-rwxr-xr-x]chef-server-webui/public/images/treeBuilderImages/folder.gifbin974 -> 974 bytes
-rw-r--r--[-rwxr-xr-x]chef-server-webui/public/images/treeBuilderImages/folderNode.gifbin133 -> 133 bytes
-rw-r--r--[-rwxr-xr-x]chef-server-webui/public/images/treeBuilderImages/folderNodeFirst.gifbin878 -> 878 bytes
-rw-r--r--[-rwxr-xr-x]chef-server-webui/public/images/treeBuilderImages/folderNodeLast.gifbin130 -> 130 bytes
-rw-r--r--[-rwxr-xr-x]chef-server-webui/public/images/treeBuilderImages/folderNodeLastFirst.gifbin872 -> 872 bytes
-rw-r--r--[-rwxr-xr-x]chef-server-webui/public/images/treeBuilderImages/folderNodeOpen.gifbin129 -> 129 bytes
-rw-r--r--[-rwxr-xr-x]chef-server-webui/public/images/treeBuilderImages/folderNodeOpenFirst.gifbin868 -> 868 bytes
-rw-r--r--[-rwxr-xr-x]chef-server-webui/public/images/treeBuilderImages/folderNodeOpenLast.gifbin125 -> 125 bytes
-rw-r--r--[-rwxr-xr-x]chef-server-webui/public/images/treeBuilderImages/folderNodeOpenLastFirst.gifbin863 -> 863 bytes
-rw-r--r--[-rwxr-xr-x]chef-server-webui/public/images/treeBuilderImages/folderOpen.gifbin254 -> 254 bytes
-rw-r--r--[-rwxr-xr-x]chef-server-webui/public/images/treeBuilderImages/vertLine.gifbin140 -> 140 bytes
-rw-r--r--[-rwxr-xr-x]chef-server-webui/public/javascripts/JSONeditor.js0
-rw-r--r--[-rwxr-xr-x]chef-server-webui/public/javascripts/jquery-1.3.2.min.js0
-rw-r--r--[-rwxr-xr-x]chef-server-webui/public/javascripts/jquery-ui-1.7.1.custom.min.js0
-rw-r--r--[-rwxr-xr-x]chef-server-webui/public/stylesheets/images/ui-bg_diagonals-small_0_aaaaaa_40x40.pngbin128 -> 128 bytes
-rw-r--r--[-rwxr-xr-x]chef-server-webui/public/stylesheets/images/ui-bg_diagonals-thick_15_444444_40x40.pngbin253 -> 253 bytes
-rw-r--r--[-rwxr-xr-x]chef-server-webui/public/stylesheets/images/ui-bg_glass_100_f0f0f0_1x400.pngbin106 -> 106 bytes
-rw-r--r--[-rwxr-xr-x]chef-server-webui/public/stylesheets/images/ui-bg_glass_50_99c2ff_1x400.pngbin123 -> 123 bytes
-rw-r--r--[-rwxr-xr-x]chef-server-webui/public/stylesheets/images/ui-bg_glass_55_fbf5d0_1x400.pngbin153 -> 153 bytes
-rw-r--r--[-rwxr-xr-x]chef-server-webui/public/stylesheets/images/ui-bg_glass_80_e6e6e6_1x400.pngbin113 -> 113 bytes
-rw-r--r--[-rwxr-xr-x]chef-server-webui/public/stylesheets/images/ui-bg_glass_95_fef1ec_1x400.pngbin119 -> 119 bytes
-rw-r--r--[-rwxr-xr-x]chef-server-webui/public/stylesheets/images/ui-bg_highlight-hard_100_f9f9f9_1x100.pngbin86 -> 86 bytes
-rw-r--r--[-rwxr-xr-x]chef-server-webui/public/stylesheets/images/ui-bg_highlight-soft_100_e7eef3_1x100.pngbin149 -> 149 bytes
-rw-r--r--[-rwxr-xr-x]chef-server-webui/public/stylesheets/images/ui-icons_222222_256x240.pngbin4379 -> 4379 bytes
-rw-r--r--[-rwxr-xr-x]chef-server-webui/public/stylesheets/images/ui-icons_2694e8_256x240.pngbin4379 -> 4379 bytes
-rw-r--r--[-rwxr-xr-x]chef-server-webui/public/stylesheets/images/ui-icons_2e83ff_256x240.pngbin4379 -> 4379 bytes
-rw-r--r--[-rwxr-xr-x]chef-server-webui/public/stylesheets/images/ui-icons_72a7cf_256x240.pngbin4379 -> 4379 bytes
-rw-r--r--[-rwxr-xr-x]chef-server-webui/public/stylesheets/images/ui-icons_888888_256x240.pngbin4379 -> 4379 bytes
-rw-r--r--[-rwxr-xr-x]chef-server-webui/public/stylesheets/images/ui-icons_cd0a0a_256x240.pngbin4379 -> 4379 bytes
-rw-r--r--[-rwxr-xr-x]chef-server-webui/public/stylesheets/images/ui-icons_ffffff_256x240.pngbin4379 -> 4379 bytes
-rw-r--r--[-rwxr-xr-x]chef-server-webui/public/stylesheets/jquery-ui-1.7.1.custom.css0
-rw-r--r--chef/Rakefile13
-rwxr-xr-xchef/distro/debian/etc/init.d/chef-client (renamed from distro/debian/etc/init.d/chef-client)0
-rwxr-xr-xchef/distro/debian/etc/init.d/chef-indexer (renamed from distro/debian/etc/init.d/chef-indexer)0
-rwxr-xr-xchef/distro/debian/etc/init.d/chef-server (renamed from distro/debian/etc/init.d/chef-server)0
-rw-r--r--chef/distro/debian/man/man1/chef-indexer.1 (renamed from distro/debian/man/man1/chef-indexer.1)0
-rw-r--r--chef/distro/debian/man/man1/chef-server.1 (renamed from distro/debian/man/man1/chef-server.1)0
-rw-r--r--chef/distro/debian/man/man8/chef-client.8 (renamed from distro/debian/man/man8/chef-client.8)0
-rw-r--r--chef/distro/debian/man/man8/chef-solo.8 (renamed from distro/debian/man/man8/chef-solo.8)0
-rw-r--r--chef/distro/redhat/etc/chef/client.rb (renamed from distro/redhat/etc/chef/client.rb)0
-rw-r--r--chef/distro/redhat/etc/chef/indexer.rb (renamed from distro/redhat/etc/chef/indexer.rb)0
-rw-r--r--chef/distro/redhat/etc/chef/server.rb (renamed from distro/redhat/etc/chef/server.rb)0
-rw-r--r--chef/distro/redhat/etc/init.d/chef-client (renamed from distro/redhat/etc/init.d/chef-client)0
-rw-r--r--chef/distro/redhat/etc/init.d/chef-indexer (renamed from distro/redhat/etc/init.d/chef-indexer)0
-rw-r--r--chef/distro/redhat/etc/init.d/chef-server (renamed from distro/redhat/etc/init.d/chef-server)0
-rw-r--r--chef/lib/chef/client.rb19
-rw-r--r--chef/lib/chef/compile.rb36
-rw-r--r--chef/lib/chef/cookbook.rb31
-rw-r--r--chef/lib/chef/cookbook_loader.rb110
-rw-r--r--chef/lib/chef/mixin/command.rb99
-rw-r--r--chef/lib/chef/mixin/convert_to_class_name.rb48
-rw-r--r--chef/lib/chef/mixin/find_preferred_file.rb19
-rw-r--r--chef/lib/chef/mixin/from_file.rb14
-rw-r--r--chef/lib/chef/mixin/generate_url.rb3
-rw-r--r--chef/lib/chef/mixin/recipe_definition_dsl_core.rb77
-rw-r--r--chef/lib/chef/platform.rb2
-rw-r--r--chef/lib/chef/provider.rb65
-rw-r--r--chef/lib/chef/provider/cron.rb94
-rw-r--r--chef/lib/chef/provider/deploy.rb281
-rw-r--r--chef/lib/chef/provider/deploy/revision.rb70
-rw-r--r--chef/lib/chef/provider/deploy/timestamped.rb33
-rw-r--r--chef/lib/chef/provider/git.rb194
-rw-r--r--chef/lib/chef/provider/group.rb4
-rw-r--r--chef/lib/chef/provider/group/gpasswd.rb50
-rw-r--r--chef/lib/chef/provider/group/groupadd.rb18
-rw-r--r--chef/lib/chef/provider/group/usermod.rb57
-rw-r--r--chef/lib/chef/provider/ifconfig.rb6
-rw-r--r--chef/lib/chef/provider/mount.rb4
-rw-r--r--chef/lib/chef/provider/mount/mount.rb4
-rw-r--r--chef/lib/chef/provider/package.rb4
-rw-r--r--chef/lib/chef/provider/package/apt.rb8
-rw-r--r--chef/lib/chef/provider/package/dpkg.rb22
-rw-r--r--chef/lib/chef/provider/package/freebsd.rb12
-rw-r--r--chef/lib/chef/provider/package/macports.rb8
-rw-r--r--chef/lib/chef/provider/package/portage.rb4
-rw-r--r--chef/lib/chef/provider/package/rpm.rb8
-rw-r--r--chef/lib/chef/provider/package/rubygems.rb6
-rw-r--r--chef/lib/chef/provider/package/yum.rb12
-rw-r--r--chef/lib/chef/provider/remote_file.rb21
-rw-r--r--chef/lib/chef/provider/service.rb10
-rw-r--r--chef/lib/chef/provider/service/init.rb4
-rw-r--r--chef/lib/chef/provider/service/redhat.rb4
-rw-r--r--chef/lib/chef/provider/subversion.rb145
-rw-r--r--chef/lib/chef/provider/template.rb2
-rw-r--r--chef/lib/chef/provider/user.rb4
-rw-r--r--chef/lib/chef/recipe.rb84
-rw-r--r--chef/lib/chef/resource.rb127
-rw-r--r--chef/lib/chef/resource/cron.rb36
-rw-r--r--chef/lib/chef/resource/deploy.rb360
-rw-r--r--chef/lib/chef/resource/deploy_revision.rb35
-rw-r--r--chef/lib/chef/resource/git.rb36
-rw-r--r--chef/lib/chef/resource/group.rb2
-rw-r--r--chef/lib/chef/resource/scm.rb129
-rw-r--r--chef/lib/chef/resource/subversion.rb33
-rw-r--r--chef/lib/chef/resource/timestamped_deploy.rb31
-rw-r--r--chef/lib/chef/resource_collection.rb36
-rw-r--r--chef/lib/chef/runner.rb63
-rw-r--r--chef/spec/data/lwrp/providers/buck_passer.rb10
-rw-r--r--chef/spec/data/lwrp/providers/buck_passer_2.rb10
-rw-r--r--chef/spec/data/lwrp/providers/embedded_resource_accesses_providers_scope.rb14
-rw-r--r--chef/spec/data/lwrp/providers/monkey_name_printer.rb3
-rw-r--r--chef/spec/data/lwrp/providers/paint_drying_watcher.rb7
-rw-r--r--chef/spec/data/lwrp/providers/thumb_twiddler.rb7
-rw-r--r--chef/spec/data/lwrp/resources/bar.rb1
-rw-r--r--chef/spec/data/lwrp/resources/foo.rb3
-rw-r--r--chef/spec/lib/chef/provider/snakeoil.rb5
-rw-r--r--chef/spec/unit/client_spec.rb36
-rw-r--r--chef/spec/unit/compile_spec.rb10
-rw-r--r--chef/spec/unit/lwrp_spec.rb143
-rw-r--r--chef/spec/unit/mixin/command_spec.rb14
-rw-r--r--chef/spec/unit/mixin/find_preferred_file_spec.rb105
-rw-r--r--chef/spec/unit/provider/cron_spec.rb161
-rw-r--r--chef/spec/unit/provider/deploy/revision_spec.rb72
-rw-r--r--chef/spec/unit/provider/deploy/timestamped_spec.rb38
-rw-r--r--chef/spec/unit/provider/deploy_spec.rb387
-rw-r--r--chef/spec/unit/provider/git_spec.rb234
-rw-r--r--chef/spec/unit/provider/group/gpasswd_spec.rb107
-rw-r--r--chef/spec/unit/provider/group/groupadd_spec.rb69
-rw-r--r--chef/spec/unit/provider/group/usermod_spec.rb109
-rw-r--r--chef/spec/unit/provider/package/apt_spec.rb18
-rw-r--r--chef/spec/unit/provider/package/dpkg_spec.rb61
-rw-r--r--chef/spec/unit/provider/package/freebsd_spec.rb26
-rw-r--r--chef/spec/unit/provider/package/macports_spec.rb16
-rw-r--r--chef/spec/unit/provider/package/portage_spec.rb10
-rw-r--r--chef/spec/unit/provider/package/rpm_spec.rb6
-rw-r--r--chef/spec/unit/provider/package/rubygems_spec.rb7
-rw-r--r--chef/spec/unit/provider/package/yum_spec.rb10
-rw-r--r--chef/spec/unit/provider/remote_file_spec.rb55
-rw-r--r--chef/spec/unit/provider/service_spec.rb27
-rw-r--r--chef/spec/unit/provider/subversion_spec.rb192
-rw-r--r--chef/spec/unit/provider_spec.rb14
-rw-r--r--chef/spec/unit/resource/cron_spec.rb20
-rw-r--r--chef/spec/unit/resource/deploy_revision_spec.rb37
-rw-r--r--chef/spec/unit/resource/deploy_spec.rb199
-rw-r--r--chef/spec/unit/resource/git_spec.rb46
-rw-r--r--chef/spec/unit/resource/group_spec.rb26
-rw-r--r--chef/spec/unit/resource/scm_spec.rb122
-rw-r--r--chef/spec/unit/resource/subversion_spec.rb36
-rw-r--r--chef/spec/unit/resource/timestamped_deploy_spec.rb28
-rw-r--r--chef/spec/unit/resource_collection_spec.rb36
-rw-r--r--chef/spec/unit/resource_spec.rb26
-rw-r--r--chef/spec/unit/runner_spec.rb21
-rw-r--r--cucumber.yml6
-rw-r--r--features/cookbooks/lightweight_resources_and_providers.feature55
-rw-r--r--features/data/cookbooks/deploy/recipes/callbacks.rb78
-rw-r--r--features/data/cookbooks/deploy/recipes/default.rb51
-rw-r--r--features/data/cookbooks/deploy/recipes/embedded_recipe_callbacks.rb70
-rw-r--r--features/data/cookbooks/deploy/recipes/revision_deploy.rb51
-rw-r--r--features/data/cookbooks/deploy/recipes/rollback.rb56
-rw-r--r--features/data/cookbooks/deploy/templates/default/app_config.yml.erb23
-rw-r--r--features/data/cookbooks/deploy/templates/default/database.yml.erb18
-rw-r--r--features/data/cookbooks/deploy/templates/default/embedded_recipe_before_symlink.rb.erb4
-rw-r--r--features/data/cookbooks/deploy/templates/default/sneaky_after_restart_hook.rb.erb4
-rw-r--r--features/data/cookbooks/deploy/templates/default/sneaky_before_migrate_hook.rb.erb2
-rw-r--r--features/data/cookbooks/deploy/templates/default/sneaky_before_restart_hook.rb.erb4
-rw-r--r--features/data/cookbooks/deploy/templates/default/sneaky_before_symlink_hook.rb.erb2
-rw-r--r--features/data/cookbooks/lwrp/providers/default.rb9
-rw-r--r--features/data/cookbooks/lwrp/providers/lwp_non_default.rb3
-rw-r--r--features/data/cookbooks/lwrp/providers/lwp_overridden_load_current_resource.rb8
-rw-r--r--features/data/cookbooks/lwrp/recipes/default_everything.rb6
-rw-r--r--features/data/cookbooks/lwrp/recipes/non_default_provider.rb6
-rw-r--r--features/data/cookbooks/lwrp/recipes/non_default_resource.rb6
-rw-r--r--features/data/cookbooks/lwrp/recipes/overridden_provider_load_current_resource.rb6
-rw-r--r--features/data/cookbooks/lwrp/recipes/overridden_resource_initialize.rb6
-rw-r--r--features/data/cookbooks/lwrp/recipes/provider_invokes_resource.rb6
-rw-r--r--features/data/cookbooks/lwrp/recipes/provider_is_a_class.rb6
-rw-r--r--features/data/cookbooks/lwrp/recipes/provider_is_a_string.rb6
-rw-r--r--features/data/cookbooks/lwrp/recipes/provider_is_a_symbol.rb6
-rw-r--r--features/data/cookbooks/lwrp/resources/default.rb4
-rw-r--r--features/data/cookbooks/lwrp/resources/lwr_non_default.rb3
-rw-r--r--features/data/cookbooks/lwrp/resources/lwr_overridden_initialize.rb8
-rw-r--r--features/data/cookbooks/scm/metadata.rb8
-rw-r--r--features/data/cookbooks/scm/recipes/git.rb25
-rw-r--r--features/data/typo.bundlebin0 -> 10605366 bytes
-rw-r--r--features/provider/deploy/deploy.feature71
-rw-r--r--features/provider/scm/git.feature17
-rw-r--r--features/steps/deploy_steps.rb59
-rw-r--r--features/steps/run_client_steps.rb4
190 files changed, 5486 insertions, 555 deletions
diff --git a/CHANGELOG b/CHANGELOG
index 2e6fdddd32..b04851fa99 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -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
diff --git a/Rakefile b/Rakefile
index 31d80fbfbd..032fdcf8de 100644
--- a/Rakefile
+++ b/Rakefile
@@ -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
index 87b4f8bd69..87b4f8bd69 100755..100644
--- a/chef-server-webui/public/facebox/closelabel.gif
+++ b/chef-server-webui/public/facebox/closelabel.gif
Binary files differ
diff --git a/chef-server-webui/public/facebox/loading.gif b/chef-server-webui/public/facebox/loading.gif
index f864d5fd38..f864d5fd38 100755..100644
--- a/chef-server-webui/public/facebox/loading.gif
+++ b/chef-server-webui/public/facebox/loading.gif
Binary files differ
diff --git a/chef-server-webui/public/images/treeBuilderImages/Thumbs.db b/chef-server-webui/public/images/treeBuilderImages/Thumbs.db
index 934ed17cb5..934ed17cb5 100755..100644
--- a/chef-server-webui/public/images/treeBuilderImages/Thumbs.db
+++ b/chef-server-webui/public/images/treeBuilderImages/Thumbs.db
Binary files differ
diff --git a/chef-server-webui/public/images/treeBuilderImages/doc.gif b/chef-server-webui/public/images/treeBuilderImages/doc.gif
index a0f4dd5dfb..a0f4dd5dfb 100755..100644
--- a/chef-server-webui/public/images/treeBuilderImages/doc.gif
+++ b/chef-server-webui/public/images/treeBuilderImages/doc.gif
Binary files differ
diff --git a/chef-server-webui/public/images/treeBuilderImages/docNode.gif b/chef-server-webui/public/images/treeBuilderImages/docNode.gif
index 40167db1a5..40167db1a5 100755..100644
--- a/chef-server-webui/public/images/treeBuilderImages/docNode.gif
+++ b/chef-server-webui/public/images/treeBuilderImages/docNode.gif
Binary files differ
diff --git a/chef-server-webui/public/images/treeBuilderImages/docNodeLast.gif b/chef-server-webui/public/images/treeBuilderImages/docNodeLast.gif
index b7b3e55cd0..b7b3e55cd0 100755..100644
--- a/chef-server-webui/public/images/treeBuilderImages/docNodeLast.gif
+++ b/chef-server-webui/public/images/treeBuilderImages/docNodeLast.gif
Binary files differ
diff --git a/chef-server-webui/public/images/treeBuilderImages/docNodeLastFirst.gif b/chef-server-webui/public/images/treeBuilderImages/docNodeLastFirst.gif
index 2cbb765d7c..2cbb765d7c 100755..100644
--- a/chef-server-webui/public/images/treeBuilderImages/docNodeLastFirst.gif
+++ b/chef-server-webui/public/images/treeBuilderImages/docNodeLastFirst.gif
Binary files differ
diff --git a/chef-server-webui/public/images/treeBuilderImages/folder.gif b/chef-server-webui/public/images/treeBuilderImages/folder.gif
index 3952b3d234..3952b3d234 100755..100644
--- a/chef-server-webui/public/images/treeBuilderImages/folder.gif
+++ b/chef-server-webui/public/images/treeBuilderImages/folder.gif
Binary files differ
diff --git a/chef-server-webui/public/images/treeBuilderImages/folderNode.gif b/chef-server-webui/public/images/treeBuilderImages/folderNode.gif
index 5b680136ee..5b680136ee 100755..100644
--- a/chef-server-webui/public/images/treeBuilderImages/folderNode.gif
+++ b/chef-server-webui/public/images/treeBuilderImages/folderNode.gif
Binary files differ
diff --git a/chef-server-webui/public/images/treeBuilderImages/folderNodeFirst.gif b/chef-server-webui/public/images/treeBuilderImages/folderNodeFirst.gif
index 9039327677..9039327677 100755..100644
--- a/chef-server-webui/public/images/treeBuilderImages/folderNodeFirst.gif
+++ b/chef-server-webui/public/images/treeBuilderImages/folderNodeFirst.gif
Binary files differ
diff --git a/chef-server-webui/public/images/treeBuilderImages/folderNodeLast.gif b/chef-server-webui/public/images/treeBuilderImages/folderNodeLast.gif
index b87f003154..b87f003154 100755..100644
--- a/chef-server-webui/public/images/treeBuilderImages/folderNodeLast.gif
+++ b/chef-server-webui/public/images/treeBuilderImages/folderNodeLast.gif
Binary files differ
diff --git a/chef-server-webui/public/images/treeBuilderImages/folderNodeLastFirst.gif b/chef-server-webui/public/images/treeBuilderImages/folderNodeLastFirst.gif
index 64c4d7ae07..64c4d7ae07 100755..100644
--- a/chef-server-webui/public/images/treeBuilderImages/folderNodeLastFirst.gif
+++ b/chef-server-webui/public/images/treeBuilderImages/folderNodeLastFirst.gif
Binary files differ
diff --git a/chef-server-webui/public/images/treeBuilderImages/folderNodeOpen.gif b/chef-server-webui/public/images/treeBuilderImages/folderNodeOpen.gif
index b43ce87bf7..b43ce87bf7 100755..100644
--- a/chef-server-webui/public/images/treeBuilderImages/folderNodeOpen.gif
+++ b/chef-server-webui/public/images/treeBuilderImages/folderNodeOpen.gif
Binary files differ
diff --git a/chef-server-webui/public/images/treeBuilderImages/folderNodeOpenFirst.gif b/chef-server-webui/public/images/treeBuilderImages/folderNodeOpenFirst.gif
index d8ee1fc1b1..d8ee1fc1b1 100755..100644
--- a/chef-server-webui/public/images/treeBuilderImages/folderNodeOpenFirst.gif
+++ b/chef-server-webui/public/images/treeBuilderImages/folderNodeOpenFirst.gif
Binary files differ
diff --git a/chef-server-webui/public/images/treeBuilderImages/folderNodeOpenLast.gif b/chef-server-webui/public/images/treeBuilderImages/folderNodeOpenLast.gif
index 11ae43a5ae..11ae43a5ae 100755..100644
--- a/chef-server-webui/public/images/treeBuilderImages/folderNodeOpenLast.gif
+++ b/chef-server-webui/public/images/treeBuilderImages/folderNodeOpenLast.gif
Binary files differ
diff --git a/chef-server-webui/public/images/treeBuilderImages/folderNodeOpenLastFirst.gif b/chef-server-webui/public/images/treeBuilderImages/folderNodeOpenLastFirst.gif
index ba5c0d168d..ba5c0d168d 100755..100644
--- a/chef-server-webui/public/images/treeBuilderImages/folderNodeOpenLastFirst.gif
+++ b/chef-server-webui/public/images/treeBuilderImages/folderNodeOpenLastFirst.gif
Binary files differ
diff --git a/chef-server-webui/public/images/treeBuilderImages/folderOpen.gif b/chef-server-webui/public/images/treeBuilderImages/folderOpen.gif
index 7df8d32261..7df8d32261 100755..100644
--- a/chef-server-webui/public/images/treeBuilderImages/folderOpen.gif
+++ b/chef-server-webui/public/images/treeBuilderImages/folderOpen.gif
Binary files differ
diff --git a/chef-server-webui/public/images/treeBuilderImages/vertLine.gif b/chef-server-webui/public/images/treeBuilderImages/vertLine.gif
index 63ee93a0f3..63ee93a0f3 100755..100644
--- a/chef-server-webui/public/images/treeBuilderImages/vertLine.gif
+++ b/chef-server-webui/public/images/treeBuilderImages/vertLine.gif
Binary files differ
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
index 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
Binary files differ
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
index 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
Binary files differ
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
index 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
Binary files differ
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
index 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
Binary files differ
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
index 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
Binary files differ
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
index 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
Binary files differ
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
index 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
Binary files differ
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
index 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
Binary files differ
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
index 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
Binary files differ
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
index 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
Binary files differ
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
index 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
Binary files differ
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
index 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
Binary files differ
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
index 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
Binary files differ
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
index 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
Binary files differ
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
index 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
Binary files differ
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
index 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
Binary files differ
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
new file mode 100644
index 0000000000..ab2e02fe50
--- /dev/null
+++ b/features/data/typo.bundle
Binary files differ
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"