summaryrefslogtreecommitdiff
path: root/spec/functional/resource/git_spec.rb
diff options
context:
space:
mode:
authorSeth Falcon <seth@opscode.com>2013-01-29 22:03:49 -0800
committerBryan McLellan <btm@opscode.com>2013-05-24 07:40:04 -0700
commit8093045e9ce178bd82773ab3659787f48cfc0172 (patch)
treed9d57c916e7d1949d0e56a55d12ad74481735f9b /spec/functional/resource/git_spec.rb
parentccec71b6c500f456f90500f7948cbc2d97b0b74d (diff)
downloadchef-8093045e9ce178bd82773ab3659787f48cfc0172.tar.gz
[CHEF-2420] Support annotated tags in the git provider
Annotated tags have their own SHA and also point to a SHA in the git repo. Using git ls-remote $REPO $TAG* the tagged SHAs can be identified by the '^{}' suffix appended to the corresponding tag name. When resolving the remote reference, we now search for $TAG* and use the SHA of the tagged commit (with ^{}) if it is there and otherwise use the SHA with an exact match for $TAG (to avoid capturing anything extra via the glob used to return refs). If no revision is specified, we force 'HEAD' in the git ls-remote call to constrain what can come back from the remote. Further, we protect against a badly chosen annotated tag named 'HEAD'. The functional test suite includes a git bundle of a small example repo. We found that locally cloning this bundle provided the closest simulation of `git ls-remote` behavior. YMMV depending on git version. The test suite could be extended to improve overall coverage, but does verify the desired behavior for annotated tags.
Diffstat (limited to 'spec/functional/resource/git_spec.rb')
-rw-r--r--spec/functional/resource/git_spec.rb229
1 files changed, 229 insertions, 0 deletions
diff --git a/spec/functional/resource/git_spec.rb b/spec/functional/resource/git_spec.rb
new file mode 100644
index 0000000000..d4578341cd
--- /dev/null
+++ b/spec/functional/resource/git_spec.rb
@@ -0,0 +1,229 @@
+#
+# Author:: Seth Falcon (<seth@opscode.com>)
+# Copyright:: Copyright (c) 2013 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 'chef/mixin/shell_out'
+require 'tmpdir'
+
+# Deploy relies heavily on symlinks, so it doesn't work on windows.
+describe Chef::Resource::Git do
+ include Chef::Mixin::ShellOut
+ let(:file_cache_path) { Dir.mktmpdir }
+ let(:deploy_directory) { Dir.mktmpdir }
+
+ 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
+ #
+ # Beware that git bundles don't behave exactly the same as real
+ # remotes. To get closer to real remotes, we'll create a local clone
+ # of the bundle to use as a remote for the tests. This at least
+ # gives the expected responses for ls-remote using git version
+ # 1.7.12.4
+ let(:git_bundle_repo) { File.expand_path("git_bundles/example-repo.gitbundle", CHEF_SPEC_DATA) }
+ let(:origin_repo_dir) { Dir.mktmpdir }
+ let(:origin_repo) { "#{origin_repo_dir}/example" }
+
+ # This is the fourth version
+ let(:v1_commit) { "bc5ec79931ae74089aeadca6edc173527613e6d9" }
+ let(:v1_tag) { "9b73fb5e316bfaff7b822b0ccb3e1e08f9885085" }
+ let(:rev_foo) { "ed181b3419b6f489bedab282348162a110d6d3a1" }
+ let(:rev_testing) { "972d153654503bccec29f630c5dd369854a561e8" }
+ let(:rev_head) { "d294fbfd05aa7709ad9a9b8ef6343b17d355bf5f"}
+
+ before(:each) do
+ @old_file_cache_path = Chef::Config[:file_cache_path]
+ shell_out!("git clone #{git_bundle_repo} example", :cwd => origin_repo_dir)
+ Chef::Config[:file_cache_path] = file_cache_path
+ end
+
+ after(:each) 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
+ end
+
+ after(:all) do
+ FileUtils.remove_entry_secure origin_repo_dir
+ end
+
+ before(:all) do
+ @ohai = Ohai::System.new
+ @ohai.require_plugin("os")
+ end
+
+ context "when deploying from an annotated tag" do
+ let(:basic_git_resource) do
+ Chef::Resource::Git.new(deploy_directory, run_context).tap do |r|
+ r.repository origin_repo
+ r.revision "v1.0.0"
+ end
+ end
+
+ # We create a copy of the basic_git_resource so that we can run
+ # the resource again and verify that it doesn't update.
+ let(:copy_git_resource) do
+ Chef::Resource::Git.new(deploy_directory, run_context).tap do |r|
+ r.repository origin_repo
+ r.revision "v1.0.0"
+ end
+ end
+
+ it "checks out the revision pointed to by the tag commit, not the tag commit itself" do
+ basic_git_resource.run_action(:sync)
+ head_rev = shell_out!('git rev-parse HEAD', :cwd => deploy_directory, :returns => [0]).stdout.strip
+ head_rev.should == v1_commit
+ # also verify the tag commit itself is what we expect as an extra sanity check
+ rev = shell_out!('git rev-parse v1.0.0', :cwd => deploy_directory, :returns => [0]).stdout.strip
+ rev.should == v1_tag
+ end
+
+ it "doesn't update if up-to-date" do
+ # this used to fail because we didn't resolve the annotated tag
+ # properly to the pointed to commit.
+ basic_git_resource.run_action(:sync)
+ head_rev = shell_out!('git rev-parse HEAD', :cwd => deploy_directory, :returns => [0]).stdout.strip
+ head_rev.should == v1_commit
+
+ copy_git_resource.run_action(:sync)
+ copy_git_resource.should_not be_updated
+ end
+ end
+
+ context "when deploying from a SHA revision" do
+ let(:basic_git_resource) do
+ Chef::Resource::Git.new(deploy_directory, run_context).tap do |r|
+ r.repository git_bundle_repo
+ end
+ end
+
+ # We create a copy of the basic_git_resource so that we can run
+ # the resource again and verify that it doesn't update.
+ let(:copy_git_resource) do
+ Chef::Resource::Git.new(deploy_directory, run_context).tap do |r|
+ r.repository origin_repo
+ end
+ end
+
+ it "checks out the expected revision ed18" do
+ basic_git_resource.revision rev_foo
+ basic_git_resource.run_action(:sync)
+ head_rev = shell_out!('git rev-parse HEAD', :cwd => deploy_directory, :returns => [0]).stdout.strip
+ head_rev.should == rev_foo
+ end
+
+ it "doesn't update if up-to-date" do
+ basic_git_resource.revision rev_foo
+ basic_git_resource.run_action(:sync)
+ head_rev = shell_out!('git rev-parse HEAD', :cwd => deploy_directory, :returns => [0]).stdout.strip
+ head_rev.should == rev_foo
+
+ copy_git_resource.revision rev_foo
+ copy_git_resource.run_action(:sync)
+ copy_git_resource.should_not be_updated
+ end
+
+ it "checks out the expected revision 972d" do
+ basic_git_resource.revision rev_testing
+ basic_git_resource.run_action(:sync)
+ head_rev = shell_out!('git rev-parse HEAD', :cwd => deploy_directory, :returns => [0]).stdout.strip
+ head_rev.should == rev_testing
+ end
+ end
+
+ context "when deploying from a revision named 'HEAD'" do
+ let(:basic_git_resource) do
+ Chef::Resource::Git.new(deploy_directory, run_context).tap do |r|
+ r.repository origin_repo
+ r.revision 'HEAD'
+ end
+ end
+
+ it "checks out the expected revision" do
+ basic_git_resource.run_action(:sync)
+ head_rev = shell_out!('git rev-parse HEAD', :cwd => deploy_directory, :returns => [0]).stdout.strip
+ head_rev.should == rev_head
+ end
+ end
+
+ context "when deploying from the default revision" do
+ let(:basic_git_resource) do
+ Chef::Resource::Git.new(deploy_directory, run_context).tap do |r|
+ r.repository origin_repo
+ # use default
+ end
+ end
+
+ it "checks out HEAD as the default revision" do
+ basic_git_resource.run_action(:sync)
+ head_rev = shell_out!('git rev-parse HEAD', :cwd => deploy_directory, :returns => [0]).stdout.strip
+ head_rev.should == rev_head
+ end
+ end
+
+ context "when dealing with a repo with a degenerate tag named 'HEAD'" do
+ before do
+ shell_out!("git tag -m\"degenerate tag\" HEAD ed181b3419b6f489bedab282348162a110d6d3a1",
+ :cwd => origin_repo)
+ end
+
+ let(:basic_git_resource) do
+ Chef::Resource::Git.new(deploy_directory, run_context).tap do |r|
+ r.repository origin_repo
+ r.revision 'HEAD'
+ end
+ end
+
+ let(:git_resource_default_rev) do
+ Chef::Resource::Git.new(deploy_directory, run_context).tap do |r|
+ r.repository origin_repo
+ # use default of revision
+ end
+ end
+
+ it "checks out the (master) HEAD revision and ignores the tag" do
+ basic_git_resource.run_action(:sync)
+ head_rev = shell_out!('git rev-parse HEAD',
+ :cwd => deploy_directory,
+ :returns => [0]).stdout.strip
+ head_rev.should == rev_head
+ end
+
+ it "checks out the (master) HEAD revision when no revision is specified (ignores tag)" do
+ git_resource_default_rev.run_action(:sync)
+ head_rev = shell_out!('git rev-parse HEAD',
+ :cwd => deploy_directory,
+ :returns => [0]).stdout.strip
+ head_rev.should == rev_head
+ end
+
+ end
+end