Skip to content
Browse files

Initial check-in.

  • Loading branch information...
0 parents commit 694fba3ea35c7156148a17be3534b87add16a257 @relistan relistan committed Apr 4, 2012
9 Gemfile
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
14 capistrano-deploytags.gemspec
@@ -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
84 lib/capistrano/deploy_tags.rb
@@ -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")}"
@tute
tute added a note Sep 24, 2013

Would you like to make it utc, or accept timezones as parameter, for remote teams?

@gavinheavyside
MyDrive Solutions member

Making it UTC seems reasonable.

@relistan - what do you think?

@relistan
relistan added a note Oct 8, 2013

+1 for UTC, that's a good idea.

@relistan
relistan added a note Oct 8, 2013

I've opened issue 14 to track that.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
+ 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
74 spec/capistrano_deploy_tags_spec.rb
@@ -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
Binary file not shown.

0 comments on commit 694fba3

Please sign in to comment.
Something went wrong with that request. Please try again.