summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authordanielsdeleo <dan@opscode.com>2012-11-05 14:35:57 -0800
committerdanielsdeleo <dan@opscode.com>2012-11-05 16:27:15 -0800
commit7184480e7d86f9567e2962ac2dd308e55ea51e8e (patch)
tree4e571ed6277afa3dd8d50ec1387ac0e0aeda196c
parentc85cb0ecfaeafaa3738ff9950f6fb97db298ae3f (diff)
downloadchef-7184480e7d86f9567e2962ac2dd308e55ea51e8e.tar.gz
Merge branch 'OC-4668'
-rw-r--r--spec/data/git_bundles/sinatra-test-app-with-callback-files.gitbundlebin0 -> 3527 bytes
-rw-r--r--spec/data/git_bundles/sinatra-test-app.gitbundlebin0 -> 2053 bytes
-rw-r--r--spec/functional/resource/deploy_revision_spec.rb499
3 files changed, 499 insertions, 0 deletions
diff --git a/spec/data/git_bundles/sinatra-test-app-with-callback-files.gitbundle b/spec/data/git_bundles/sinatra-test-app-with-callback-files.gitbundle
new file mode 100644
index 0000000000..24e36f7eeb
--- /dev/null
+++ b/spec/data/git_bundles/sinatra-test-app-with-callback-files.gitbundle
Binary files differ
diff --git a/spec/data/git_bundles/sinatra-test-app.gitbundle b/spec/data/git_bundles/sinatra-test-app.gitbundle
new file mode 100644
index 0000000000..43c54eae5c
--- /dev/null
+++ b/spec/data/git_bundles/sinatra-test-app.gitbundle
Binary files differ
diff --git a/spec/functional/resource/deploy_revision_spec.rb b/spec/functional/resource/deploy_revision_spec.rb
new file mode 100644
index 0000000000..a9d5b4800e
--- /dev/null
+++ b/spec/functional/resource/deploy_revision_spec.rb
@@ -0,0 +1,499 @@
+#
+# Author:: Daniel DeLeo (<dan@opscode.com>)
+# Copyright:: Copyright (c) 2012 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 'spec_helper'
+require 'tmpdir'
+
+# Deploy relies heavily on symlinks, so it doesn't work on windows.
+describe Chef::Resource::DeployRevision, :unix_only => true do
+
+ let(:file_cache_path) { Dir.mktmpdir }
+ let(:deploy_directory) { Dir.mktmpdir }
+
+ # By making restart or other operations write to this file, we can externally
+ # track the order in which those operations happened.
+ let(:observe_order_file) { Tempfile.new("deploy-resource-observe-operations") }
+
+ before do
+ @old_file_cache_path = Chef::Config[:file_cache_path]
+ Chef::Config[:file_cache_path] = file_cache_path
+ end
+
+ after do
+ Chef::Config[:file_cache_path] = @old_file_cache_path
+ FileUtils.remove_entry_secure deploy_directory if File.exist?(deploy_directory)
+ FileUtils.remove_entry_secure file_cache_path
+ observe_order_file.close
+ FileUtils.remove_entry_secure observe_order_file
+ end
+
+ before(:all) do
+ @ohai = Ohai::System.new
+ @ohai.require_plugin("os")
+ end
+
+ let(:node) do
+
+ Chef::Node.new.tap do |n|
+ n.name "rspec-test"
+ n.consume_external_attrs(@ohai.data, {})
+ end
+ end
+
+ let(:event_dispatch) { Chef::EventDispatch::Dispatcher.new }
+ let(:run_context) { Chef::RunContext.new(node, {}, event_dispatch) }
+
+
+ # These tests use git's bundle feature, which is a way to export an entire
+ # git repo (or subset of commits) as a single file.
+ #
+ # Generally you can treat a git bundle as a regular git remote.
+ #
+ # See also: http://git-scm.com/2010/03/10/bundles.html
+ let(:git_bundle_repo) { File.expand_path("git_bundles/sinatra-test-app.gitbundle", CHEF_SPEC_DATA) }
+
+ let(:git_bundle_with_in_repo_callbacks) { File.expand_path("git_bundles/sinatra-test-app-with-callback-files.gitbundle", CHEF_SPEC_DATA) }
+
+ # This is the fourth version
+ let(:latest_rev) { "3eb5ca6c353c83d9179dd3b29347539829b401f3" }
+
+ # This is the third version
+ let(:previous_rev) { "6d19a6dbecc8e37f5b2277345885c0c783eb8fb1" }
+
+
+ # This is the sixth version, it is on the "with-deploy-scripts" branch
+ let(:rev_with_in_repo_callbacks) { "2404d015882659754bdb93ad6e4b4d3d02691a82" }
+
+ # Read values from the +observe_order_file+ and split each line. This way you
+ # can see in which order things really happened.
+ def actual_operations_order
+ IO.read(observe_order_file.path).split("\n").map(&:strip)
+ end
+
+ # 1. touch `restart.txt` in cwd so we know that the command is run with the
+ # right cwd.
+ # 2. Append +tag+ to the `observe_order_file` so we can check the order in
+ # which operations happen later in the test.
+ def shell_restart_command(tag)
+ "touch restart.txt && echo '#{tag}' >> #{observe_order_file.path}"
+ end
+
+ let(:basic_deploy_resource) do
+ Chef::Resource::DeployRevision.new(deploy_directory, run_context).tap do |r|
+ r.repo git_bundle_repo
+ r.symlink_before_migrate({})
+ r.symlinks({})
+ end
+ end
+
+ let(:deploy_to_latest_rev) do
+ basic_deploy_resource.dup.tap do |r|
+ r.revision(latest_rev)
+ r.restart_command shell_restart_command(:deploy_to_latest_rev)
+ end
+ end
+
+ let(:deploy_to_previous_rev) do
+ basic_deploy_resource.dup.tap do |r|
+ r.revision(previous_rev)
+ r.restart_command shell_restart_command(:deploy_to_previous_rev)
+ end
+ end
+
+ let(:deploy_to_latest_rev_again) do
+ basic_deploy_resource.dup.tap do |r|
+ r.revision(latest_rev)
+ r.restart_command shell_restart_command(:deploy_to_latest_rev_again)
+ end
+ end
+
+ # Computes the full path for +path+ relative to the deploy directory
+ def rel_path(path)
+ File.expand_path(path, deploy_directory)
+ end
+
+ def actual_current_rev
+ Dir.chdir(rel_path("current")) do
+ `git rev-parse HEAD`.strip
+ end
+ end
+
+ def self.the_app_is_deployed_at_revision(target_rev_spec)
+ it "deploys the app to the target revision (#{target_rev_spec})" do
+ target_rev = send(target_rev_spec)
+
+ File.should exist(rel_path("current"))
+
+ actual_current_rev.should == target_rev
+
+ # Is the app code actually there?
+ File.should exist(rel_path("current/app/app.rb"))
+ end
+ end
+
+ context "when deploying a simple app" do
+ describe "for the first time, with the required directory layout precreated" do
+ before do
+ FileUtils.mkdir_p(rel_path("releases"))
+ FileUtils.mkdir_p(rel_path("shared"))
+ deploy_to_latest_rev.run_action(:deploy)
+ end
+
+ the_app_is_deployed_at_revision(:latest_rev)
+
+ it "restarts the application" do
+ File.should exist(rel_path("current/restart.txt"))
+ actual_operations_order.should == %w[deploy_to_latest_rev]
+ end
+
+ it "is marked as updated" do
+ deploy_to_latest_rev.should be_updated_by_last_action
+ end
+ end
+
+ describe "back to a previously deployed revision, with the directory structure precreated" do
+ before do
+ FileUtils.mkdir_p(rel_path("releases"))
+ FileUtils.mkdir_p(rel_path("shared"))
+
+ deploy_to_latest_rev.run_action(:deploy)
+ deploy_to_previous_rev.run_action(:deploy)
+ deploy_to_latest_rev_again.run_action(:deploy)
+ end
+
+ the_app_is_deployed_at_revision(:latest_rev)
+
+ it "restarts the application after rolling back" do
+ actual_operations_order.should == %w[deploy_to_latest_rev deploy_to_previous_rev deploy_to_latest_rev_again]
+ end
+
+ it "is marked updated" do
+ deploy_to_latest_rev_again.should be_updated_by_last_action
+ end
+
+ it "deploys the right code" do
+ IO.read(rel_path("current/app/app.rb")).should include("this is the fourth version of the app")
+ end
+ end
+
+ describe "for the first time, with no existing directory layout" do
+ before do
+ deploy_to_latest_rev.run_action(:deploy)
+ end
+
+ it "creates the required directory tree" do
+ File.should be_directory(rel_path("releases"))
+ File.should be_directory(rel_path("shared"))
+ File.should be_directory(rel_path("releases/#{latest_rev}"))
+
+ File.should be_directory(rel_path("current/tmp"))
+ File.should be_directory(rel_path("current/config"))
+ File.should be_directory(rel_path("current/public"))
+
+ File.should be_symlink(rel_path("current"))
+ File.readlink(rel_path("current")).should == rel_path("releases/#{latest_rev}")
+ end
+
+ the_app_is_deployed_at_revision(:latest_rev)
+
+ it "restarts the application" do
+ File.should exist(rel_path("current/restart.txt"))
+ actual_operations_order.should == %w[deploy_to_latest_rev]
+ end
+
+ it "is marked as updated" do
+ deploy_to_latest_rev.should be_updated_by_last_action
+ end
+ end
+
+ describe "again to the current revision" do
+ before do
+ deploy_to_latest_rev.run_action(:deploy)
+ deploy_to_latest_rev.run_action(:deploy)
+ end
+
+ the_app_is_deployed_at_revision(:latest_rev)
+
+ it "does not restart the app" do
+ actual_operations_order.should == %w[deploy_to_latest_rev]
+ end
+
+ it "is not marked updated" do
+ deploy_to_latest_rev.should_not be_updated_by_last_action
+ end
+
+ end
+
+ describe "again with force_deploy" do
+ before do
+ deploy_to_latest_rev.run_action(:force_deploy)
+ deploy_to_latest_rev_again.run_action(:force_deploy)
+ end
+
+ the_app_is_deployed_at_revision(:latest_rev)
+
+ it "restarts the app" do
+ actual_operations_order.should == %w[deploy_to_latest_rev deploy_to_latest_rev_again]
+ end
+
+ it "is marked updated" do
+ deploy_to_latest_rev.should be_updated_by_last_action
+ end
+
+ end
+
+ describe "again to a new revision" do
+ before do
+ deploy_to_previous_rev.run_action(:deploy)
+ deploy_to_latest_rev.run_action(:deploy)
+ end
+
+ the_app_is_deployed_at_revision(:latest_rev)
+
+ it "restarts the application after the new deploy" do
+ actual_operations_order.should == %w[deploy_to_previous_rev deploy_to_latest_rev]
+ end
+
+ it "is marked updated" do
+ deploy_to_previous_rev.should be_updated_by_last_action
+ end
+ end
+
+ describe "back to a previously deployed revision (implicit rollback)" do
+ before do
+ deploy_to_latest_rev.run_action(:deploy)
+ deploy_to_previous_rev.run_action(:deploy)
+ deploy_to_latest_rev_again.run_action(:deploy)
+ end
+
+ the_app_is_deployed_at_revision(:latest_rev)
+
+ it "restarts the application after rolling back" do
+ actual_operations_order.should == %w[deploy_to_latest_rev deploy_to_previous_rev deploy_to_latest_rev_again]
+ end
+
+ it "is marked updated" do
+ deploy_to_latest_rev_again.should be_updated_by_last_action
+ end
+
+ it "deploys the right code" do
+ IO.read(rel_path("current/app/app.rb")).should include("this is the fourth version of the app")
+ end
+ end
+
+ # CHEF-3435
+ describe "to a deploy_to path that does not yet exist" do
+
+ let(:top_level_tmpdir) { Dir.mktmpdir }
+
+ # override top level deploy_directory let block with one that is two
+ # directories deeper
+ let(:deploy_directory) { File.expand_path("nested/deeper", top_level_tmpdir) }
+
+ after do
+ FileUtils.remove_entry_secure top_level_tmpdir
+ end
+
+ before do
+ File.should_not exist(deploy_directory)
+ deploy_to_latest_rev.run_action(:deploy)
+ end
+
+ it "creates the required directory tree" do
+ File.should be_directory(rel_path("releases"))
+ File.should be_directory(rel_path("shared"))
+ File.should be_directory(rel_path("releases/#{latest_rev}"))
+
+ File.should be_directory(rel_path("current/tmp"))
+ File.should be_directory(rel_path("current/config"))
+ File.should be_directory(rel_path("current/public"))
+
+ File.should be_symlink(rel_path("current"))
+ File.readlink(rel_path("current")).should == rel_path("releases/#{latest_rev}")
+ end
+
+ the_app_is_deployed_at_revision(:latest_rev)
+
+ end
+ end
+
+ context "when deploying an app with inline recipe callbacks" do
+
+ # Use closures to capture and mutate this variable. This allows us to track
+ # ordering of operations.
+ callback_order = []
+
+ let(:deploy_to_latest_with_inline_recipes) do
+ deploy_to_latest_rev.dup.tap do |r|
+ r.symlink_before_migrate "config/config.ru" => "config.ru"
+ r.before_migrate do
+ callback_order << :before_migrate
+
+ file "#{release_path}/before_migrate.txt" do
+ # The content here isn't relevant, but it gets printed when running
+ # the tests. Could be handy for debugging.
+ content callback_order.inspect
+ end
+ end
+ r.before_symlink do
+ callback_order << :before_symlink
+
+ current_release_path = release_path
+ ruby_block "ensure before symlink" do
+ block do
+ if File.exist?(File.join(current_release_path, "/tmp"))
+ raise "Ordering issue with provider, expected symlinks to not have been created"
+ end
+ end
+ end
+
+ file "#{release_path}/before_symlink.txt" do
+ content callback_order.inspect
+ end
+ end
+ r.before_restart do
+ callback_order << :before_restart
+
+ current_release_path = release_path
+ ruby_block "ensure after symlink" do
+ block do
+ unless File.exist?(File.join(current_release_path, "/tmp"))
+ raise "Ordering issue with provider, expected symlinks to have been created"
+ end
+ end
+ end
+
+ file "#{release_path}/tmp/before_restart.txt" do
+ content callback_order.inspect
+ end
+ end
+ r.after_restart do
+ callback_order << :after_restart
+ file "#{release_path}/tmp/after_restart.txt" do
+ content callback_order.inspect
+ end
+ end
+ end
+ end
+
+ before do
+ callback_order.clear # callback_order variable is global for this context group
+ deploy_to_latest_with_inline_recipes.run_action(:deploy)
+ end
+
+ the_app_is_deployed_at_revision(:latest_rev)
+
+ it "is marked updated" do
+ deploy_to_latest_with_inline_recipes.should be_updated_by_last_action
+ end
+
+ it "calls the callbacks in order" do
+ callback_order.should == [:before_migrate, :before_symlink, :before_restart, :after_restart]
+ end
+
+ it "runs chef resources in the callbacks" do
+ File.should exist(rel_path("current/before_migrate.txt"))
+ File.should exist(rel_path("current/before_symlink.txt"))
+ File.should exist(rel_path("current/tmp/before_restart.txt"))
+ File.should exist(rel_path("current/tmp/after_restart.txt"))
+ end
+ end
+
+ context "when deploying an app with in-repo callback scripts" do
+ let(:deploy_with_in_repo_callbacks) do
+ basic_deploy_resource.dup.tap do |r|
+ r.repo git_bundle_with_in_repo_callbacks
+ r.revision rev_with_in_repo_callbacks
+ end
+ end
+
+ before do
+ deploy_with_in_repo_callbacks.run_action(:deploy)
+ end
+
+ the_app_is_deployed_at_revision(:rev_with_in_repo_callbacks)
+
+ it "runs chef resources in the callbacks" do
+ File.should exist(rel_path("current/before_migrate.txt"))
+ File.should exist(rel_path("current/before_symlink.txt"))
+ File.should exist(rel_path("current/tmp/before_restart.txt"))
+ File.should exist(rel_path("current/tmp/after_restart.txt"))
+ end
+
+ end
+
+ context "when deploying an app with migrations" do
+ let(:deploy_with_migration) do
+ basic_deploy_resource.dup.tap do |r|
+
+ # Need this so we can call methods from this test inside the inline
+ # recipe callbacks
+ spec_context = self
+
+ r.revision latest_rev
+
+ # enable migrations
+ r.migrate true
+ # abuse `shell_restart_command` so we can observe order of when the
+ # miration command gets run
+ r.migration_command shell_restart_command("migration")
+ r.before_migrate do
+
+ # inline recipe callbacks don't cwd, so you have to get the release
+ # directory as a local and "capture" it in the closure.
+ current_release = release_path
+ execute spec_context.shell_restart_command("before_migrate") do
+ cwd current_release
+ end
+ end
+ r.before_symlink do
+ current_release = release_path
+ execute spec_context.shell_restart_command("before_symlink") do
+ cwd current_release
+ end
+ end
+
+ r.before_restart do
+ current_release = release_path
+ execute spec_context.shell_restart_command("before_restart") do
+ cwd current_release
+ end
+ end
+
+ r.after_restart do
+ current_release = release_path
+ execute spec_context.shell_restart_command("after_restart") do
+ cwd current_release
+ end
+ end
+
+ end
+ end
+
+ before do
+ deploy_with_migration.run_action(:deploy)
+ end
+
+ it "runs migrations in between the before_migrate and before_symlink steps" do
+ actual_operations_order.should == %w[before_migrate migration before_symlink before_restart after_restart]
+ end
+ end
+
+end
+
+