Initial check-in.

This commit is contained in:
Karl Matthias
2012-04-04 10:15:53 +01:00
commit 694fba3ea3
9 changed files with 327 additions and 0 deletions

9
Gemfile Normal file
View File

@@ -0,0 +1,9 @@
source "https://rubygems.org"
group :test do
gem 'rake'
gem 'rspec'
gem 'capistrano'
gem 'capistrano-ext'
gem 'capistrano-spec', :git => 'git://github.com/mydrive/capistrano-spec.git'
end

45
Gemfile.lock Normal file
View File

@@ -0,0 +1,45 @@
GIT
remote: git://github.com/mydrive/capistrano-spec.git
revision: dcc0908dc00872272b1d495bcce70e82240fa8f7
specs:
capistrano-spec (0.1.0)
GEM
remote: https://rubygems.org/
specs:
capistrano (2.11.2)
highline
net-scp (>= 1.0.0)
net-sftp (>= 2.0.0)
net-ssh (>= 2.0.14)
net-ssh-gateway (>= 1.1.0)
capistrano-ext (1.2.1)
capistrano (>= 1.0.0)
diff-lcs (1.1.3)
highline (1.6.11)
net-scp (1.0.4)
net-ssh (>= 1.99.1)
net-sftp (2.0.5)
net-ssh (>= 2.0.9)
net-ssh (2.3.0)
net-ssh-gateway (1.1.0)
net-ssh (>= 1.99.1)
rake (0.9.2.2)
rspec (2.8.0)
rspec-core (~> 2.8.0)
rspec-expectations (~> 2.8.0)
rspec-mocks (~> 2.8.0)
rspec-core (2.8.0)
rspec-expectations (2.8.0)
diff-lcs (~> 1.1.2)
rspec-mocks (2.8.0)
PLATFORMS
ruby
DEPENDENCIES
capistrano
capistrano-ext
capistrano-spec!
rake
rspec

26
LICENSE Normal file
View File

@@ -0,0 +1,26 @@
Copyright (c) 2012 MyDrive Solutions Limited, All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following
disclaimer in the documentation and/or other materials provided
with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

55
README.md Normal file
View File

@@ -0,0 +1,55 @@
Capistrano Deployment Tags
==========================
This plugin to Capistrano will add tags for each deployment that
you make to the project. It is intnded to be used with the multistage
plugin and will tag each release by environment. Each successful
deployment will result in a new tag as well as moving an existing
tag that points to the last deployment.
What It Does
------------
If I were to issue the command:
`cap production deploy`
This would result in one new git tag with the environment and
timestamp:
`production-2012.04.02-203155`
It would also result in moving or creating this tag:
`production-latest`
These tags can be used for any number of useful things including
generating statistics about deployments per day/week/year, tracking
code size over a period of time, detecting Rails migrations, and
probably a thousand other things I haven't thought of.
Usage
-----
If you use Bundler, be sure to add the gem to your Gemfile.
In your Capistrano `config/deploy.rb` you should add:
`require 'capistrano_deploytags'`
This will create two tasks, one that runs before deployment and one
that runs after.
NOTE: You will be creating and pushing tags from the version of the
code in the current checkout. This plugin needs to be run from a
clean checkout of your codebase. You should be deploying from a
clean checkout anyway, so in most cases this is not a restriction
on how you already do things. The plugin will check if your code
is clean and complain if it is not.
Credits
-------
This software was written by [Karl Matthias](https://github.com/relistan)
with help from [Gavin Heavyside](https://github.com/hgavin) and the
support of [MyDrive Solutions Limited](http://mydrivesolutions.com).
License
-------
This plugin is released under the BSD two clause license which is
available in both the Ruby Gem and the source repository.

20
Rakefile Normal file
View File

@@ -0,0 +1,20 @@
require 'rake'
$:.unshift(File.expand_path("lib"))
begin
require 'rspec/core/rake_task'
RSpec::Core::RakeTask.new(:spec) do |t|
t.rspec_opts = %w[--color --format=documentation]
t.pattern = "spec/**/*_spec.rb"
end
task :default => [:spec]
rescue LoadError
# don't generate Rspec tasks if we don't have it installed
end
task :build do
system "gem build capistrano-deploytags.gemspec"
end

View File

@@ -0,0 +1,14 @@
Gem::Specification.new do |s|
s.name = 'capistrano_deploytags'
s.version = '0.5'
s.date = '2012-04-02'
s.summary = "Add dated, environment-specific tags to your git repo at each deployment."
s.description = s.summary
s.authors = ["Karl Matthias"]
s.email = 'relistan@gmail.com'
s.files = Dir.glob("lib/**/*") + %w{ README.md LICENSE }
s.homepage = 'http://github.com/mydrive/capistrano_deploytags'
s.add_dependency 'capistrano'
s.add_dependency 'capistrano-ext'
s.require_path = 'lib'
end

View File

@@ -0,0 +1,84 @@
module Capistrano
module DeployTags
def pending_git_changes?
# Do we have any changes vs HEAD on deployment branch?
!(`git fetch && git diff #{branch} --shortstat`.strip.empty?)
end
def git_tag_for(stage)
"#{stage}-#{Time.now.strftime("%Y.%m.%d-%H%M%S")}"
end
def last_git_tag_for(stage)
"#{stage}-latest"
end
def safe_run(*args)
raise "#{args.join(" ")} failed!" unless system(*args)
end
def validate_git_vars
unless exists?(:branch) && exists?(:stage)
logger.log Capistrano::Logger::IMPORTANT, "Capistrano Deploytags requires that :branch and :stage be defined."
raise 'define :branch or :stage'
end
end
def git_tag?(tag)
!`git tag -l #{tag}`.strip.empty?
end
def has_remote?
!`git remote`.strip.empty?
end
def self.load_into(configuration)
configuration.load do
before :deploy, 'git:prepare_tree'
after :deploy, 'git:deploytags'
desc 'prepare git tree so we can tag on successful deployment'
namespace :git do
task :prepare_tree, :except => { :no_release => true } do
cdt.validate_git_vars
logger.log Capistrano::Logger::IMPORTANT, "Preparing to deploy HEAD from branch '#{branch}' to '#{stage}'"
if cdt.pending_git_changes?
logger.log Capistrano::Logger::IMPORTANT, "Whoa there, partner. Dirty trees can't deploy. Git yerself clean first."
raise 'Dirty git tree'
end
cdt.safe_run "git", "checkout", branch
cdt.safe_run "git", "pull", "origin", branch if cdt.has_remote?
end
desc 'add git tags for each successful deployment'
task :tagdeploy, :except => { :no_release => true } do
cdt.validate_git_vars
current_sha = `git rev-parse #{branch} HEAD`.strip[0..8]
logger.log Capistrano::Logger::INFO, "Tagging #{current_sha} for deployment"
tag_user = (ENV['USER'] || ENV['USERNAME']).strip
cdt.safe_run "git", "tag", "-a", cdt.git_tag_for(stage), "-m", "#{tag_user} deployed #{current_sha} to #{stage}"
if cdt.git_tag?(cdt.last_git_tag_for(stage))
cdt.safe_run "git", "tag", "-d", cdt.last_git_tag_for(stage)
cdt.safe_run "git", "push", "--tags" if cdt.has_remote?
end
cdt.safe_run "git", "tag", "-a", cdt.last_git_tag_for(stage), "-m", "#{tag_user} deployed #{current_sha} to #{stage}"
cdt.safe_run "git", "push", "--tags" if cdt.has_remote?
end
end
end
end
end
end
Capistrano.plugin :cdt, Capistrano::DeployTags
if Capistrano::Configuration.instance
Capistrano::DeployTags.load_into(Capistrano::Configuration.instance(:must_exist))
end

View File

@@ -0,0 +1,74 @@
require 'capistrano'
require 'capistrano-spec'
require 'fileutils'
mypath = File.expand_path(File.dirname(__FILE__))
require File.expand_path(File.join(mypath, '..', 'lib', 'capistrano', 'deploy_tags'))
describe Capistrano::DeployTags do
let(:configuration) { Capistrano::Configuration.new }
let(:tmpdir) { "/tmp/#{$$}" }
let(:mypath) { mypath }
before :each do
Capistrano::DeployTags.load_into(configuration)
end
def with_clean_repo(&block)
FileUtils.rm_rf tmpdir
FileUtils.mkdir tmpdir
FileUtils.chdir tmpdir
raise unless system("/usr/bin/tar xzf #{File.join(mypath, 'fixtures', 'git-fixture.tar.gz')}")
FileUtils.chdir "#{tmpdir}/git-fixture"
yield
FileUtils.rm_rf tmpdir
end
context "prepare_tree" do
it "raises an error when not in a git tree" do
FileUtils.chdir '/tmp'
configuration.set(:branch, 'master')
configuration.set(:stage, 'test')
lambda { configuration.find_and_execute_task('git:prepare_tree') }.should raise_error('git checkout master failed!')
end
context "with a clean git tree" do
it "raises an error if :stage or :branch are undefined" do
with_clean_repo do
lambda { configuration.find_and_execute_task('git:prepare_tree') }.should raise_error('define :branch or :stage')
end
end
it "does not raise an error when run from a clean tree" do
with_clean_repo do
configuration.set(:branch, 'master')
configuration.set(:stage, 'test')
lambda { configuration.find_and_execute_task('git:prepare_tree') }.should_not raise_error
end
end
end
end
context "tagdeploy" do
before :each do
configuration.set(:branch, 'master')
configuration.set(:stage, 'test')
end
it "does not raise an error when run from a clean tree" do
with_clean_repo do
lambda { configuration.find_and_execute_task('git:tagdeploy') }.should_not raise_error
end
end
it "adds appropriate git tags" do
with_clean_repo do
configuration.find_and_execute_task('git:tagdeploy')
tags = `git tag -l`.split(/\n/).sort
tags.should have(2).items
tags.first.should =~ /^test-\d{4}\.\d{2}\.\d{2}/
tags.last.should == 'test-latest'
end
end
end
end

BIN
spec/fixtures/git-fixture.tar.gz vendored Normal file

Binary file not shown.