capistrano-conditional 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/README.md +99 -0
- data/Rakefile +1 -0
- data/capistrano-conditional.gemspec +25 -0
- data/lib/capistrano-conditional/deploy.rb +99 -0
- data/lib/capistrano-conditional/integration.rb +16 -0
- data/lib/capistrano-conditional/unit.rb +71 -0
- data/lib/capistrano-conditional/version.rb +5 -0
- data/lib/capistrano-conditional.rb +5 -0
- metadata +102 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
# capistrano-conditional
|
2
|
+
|
3
|
+
This gem extends capistrano deployments to allow certain tasks to only be run under certain conditions -- i.e. conditionally.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Add to your Gemfile:
|
8
|
+
|
9
|
+
group :development do
|
10
|
+
gem 'capistrano-conditional', :require => false # <-- This is important!
|
11
|
+
end
|
12
|
+
|
13
|
+
And then modify your deploy.rb to include this at the top:
|
14
|
+
|
15
|
+
require "capistrano-conditional"
|
16
|
+
|
17
|
+
## Requirements
|
18
|
+
|
19
|
+
Your application must already be using capistrano for deployments, and (for now at least) you need to be using git.
|
20
|
+
|
21
|
+
## Usage Instructions
|
22
|
+
|
23
|
+
<code>capistrano-conditional</code> adds logic to be run before <code>cap deploy</code> or <code>cap deploy:migrations</code> that compares the local (to be deployed) code with the existing remote (currently deployed) code and lists all files that will be updated by the current deploy. It then checks the list of conditional statements that you've provided and runs any that you want run -- e.g. if you're using [whenever](https://github.com/javan/whenever) and you only want to run the <code>deploy:update_crontab</code> task if <code>config/schedule.rb</code> has been changed, you'd add a block like this to your deploy.rb:
|
24
|
+
|
25
|
+
ConditionalDeploy.register :whenever, :watchlist => 'config/schedule.rb' do
|
26
|
+
after "deploy:symlink", "deploy:update_crontab"
|
27
|
+
end
|
28
|
+
|
29
|
+
This example registers a conditional named "whenever" (names aren't programmatically important, but they're used to report what's going to be run at the beginning of each deploy). The contents of the block will be run only if the list of changed files includes a path that matches <code>config/schedule.rb</code>.
|
30
|
+
|
31
|
+
### Available Conditions
|
32
|
+
|
33
|
+
There are currently four logic conditions available (well, five, but <code>:watchlist</code> is just an alias for <code>:any_match</code>):
|
34
|
+
|
35
|
+
* <code>:any_match</code> => file_list
|
36
|
+
* <code>:none_match</code> => file_list
|
37
|
+
* <code>:if</code> => Proc
|
38
|
+
* <code>:unless</code> => Proc
|
39
|
+
|
40
|
+
Where file_list is either a string or an array of strings which will be <em>matched</em> against the list of changed filenames from git (so <code>:any_match => ['db/migrate']</code> would be true if ANY migration file was added, modified, or deleted).
|
41
|
+
|
42
|
+
<code>:any_match</code> (aliased as <code>:watchlist</code>) executes the block if ANY of the provided strings match ANY of file paths git reports changed.
|
43
|
+
|
44
|
+
<code>:none_match</code> executes the block if NONE of the provided strings match ANY of file paths git reports changed.
|
45
|
+
|
46
|
+
If you need more custom control, <code>:if</code> and <code>:unless</code> expect a Proc (which will be passed the list of changed files, if one argument is expected, or the list of changes and the git object itself, if two arguments are expected and you really want to dive into things yourself).
|
47
|
+
|
48
|
+
## Example Usage
|
49
|
+
|
50
|
+
ConditionalDeploy.register :whenever, :watchlist => 'config/schedule.rb' do
|
51
|
+
after "deploy:symlink", "deploy:update_crontab"
|
52
|
+
end
|
53
|
+
|
54
|
+
ConditionalDeploy.register :sphinx, :watchlist => ['db/schema.rb', 'db/migrate'] do
|
55
|
+
before "deploy:update_code", "thinking_sphinx:stop"
|
56
|
+
before "deploy:start", "thinking_sphinx:start"
|
57
|
+
before "deploy:restart", "thinking_sphinx:start"
|
58
|
+
end
|
59
|
+
|
60
|
+
ConditionalDeploy.register :jammit, :watchlist => ['public/images/embed', 'public/stylesheets', 'public/javascripts', 'public/assets', 'config/assets.yml'] do
|
61
|
+
after 'deploy:symlink', 'deploy:rebuild_assets'
|
62
|
+
end
|
63
|
+
|
64
|
+
# Restart the resque workers unless the only changes were to static assets, views, or controllers.
|
65
|
+
ConditionalDeploy.register(:resque, :unless => lambda { |changed| changed.all?{|f| f['public/'] || f['app/controllers/'] || f['app/views/'] } }) do
|
66
|
+
before "deploy:restart", "resque:workers:restart"
|
67
|
+
end
|
68
|
+
|
69
|
+
# ... note that you still have to actually DEFINE the tasks laid out above (e.g. deploy:update_crontab)
|
70
|
+
|
71
|
+
|
72
|
+
I've got <code>cap deploy</code> in muscle memory, and I used to find myself forgetting to run <code>cap deploy:migrations</code> until after I tested the new changes and found staging wasn't working right. I now add the following code to my apps, so I never have to worry about it again:
|
73
|
+
|
74
|
+
if ARGV.any?{|v| v['deploy:migrations']} # If running deploy:migrations
|
75
|
+
# If there weren't any changes to migrations or the schema file, then abort the deploy
|
76
|
+
ConditionalDeploy.register :unneeded_migrations, :none_match => ['db/schema.rb', 'db/migrate'] do
|
77
|
+
abort "You're running migrations, but it doesn't look like you need to!"
|
78
|
+
end
|
79
|
+
else # If NOT running deploy:migrations
|
80
|
+
# If there were changes to migration files, run migrations as part of the deployment
|
81
|
+
ConditionalDeploy.register :forgotten_migrations, :any_match => ['db/schema.rb', 'db/migrate'], :msg => "Forgot to run migrations? It's cool, we'll do it for you." do
|
82
|
+
after "deploy:update_code", "deploy:migrate"
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
Since I use it on ever project, I've wrapped that logic up in a single command:
|
87
|
+
|
88
|
+
ConditionalDeploy.monitor_migrations(self)
|
89
|
+
|
90
|
+
## Advanced Usage
|
91
|
+
|
92
|
+
By default <code>capistrano-conditional</code> will abort the deployment if you have uncommited changes in your working directory. You can skip this check on an individual run by setting the ALLOW_UNCOMMITED environment variable (e.g. <code>cap deploy ALLOW_UNCOMMITTED=1</code>).
|
93
|
+
|
94
|
+
If you need to force a particular conditional to run, you can also do that via the environment. Given the examples above, if you want to run the conditional named <code>whenever</code> even though config/schedule.rb hasn't been changed, just run <code>code deploy RUN_WHENEVER=1</code>.
|
95
|
+
|
96
|
+
## License
|
97
|
+
|
98
|
+
Copyright © 2011 [Deviantech, Inc.](http://www.deviantech.com) and released under the MIT license.
|
99
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "capistrano-conditional/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "capistrano-conditional"
|
7
|
+
s.version = Capistrano::Conditional::VERSION
|
8
|
+
s.authors = ["Kali Donovan"]
|
9
|
+
s.email = ["kali@deviantech.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{Adds support for conditional deployment tasks in capistrano}
|
12
|
+
s.description = %q{Allows making tasks for git-based projects conditional based on the specific files to be deployed.}
|
13
|
+
|
14
|
+
s.rubyforge_project = "capistrano-conditional"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
# s.add_development_dependency "rspec"
|
23
|
+
s.add_runtime_dependency "git"
|
24
|
+
s.add_runtime_dependency "capistrano"
|
25
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# This class handles the logic associated with checking if each conditional
|
2
|
+
# statement applies to a given deploy and, if so, applying them.
|
3
|
+
#
|
4
|
+
# The only publicly-useful method is <em>ConditionalDeploy.register</em>, which
|
5
|
+
# is used in deploy.rb to add conditional elements (see README for details).
|
6
|
+
class ConditionalDeploy
|
7
|
+
|
8
|
+
@@conditionals = []
|
9
|
+
|
10
|
+
def self.register(name, opts, &block)
|
11
|
+
raise("Already added a conditional with that name") if @@conditionals.any?{|c| c.name == name}
|
12
|
+
@@conditionals << Capistrano::Conditional::Unit.new(name, opts, block)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.monitor_migrations(context)
|
16
|
+
if ARGV.any?{|v| v['deploy:migrations']} # If running deploy:migrations
|
17
|
+
# If there weren't any changes to migrations or the schema file, then abort the deploy
|
18
|
+
ConditionalDeploy.register :unneeded_migrations, :none_match => ['db/schema.rb', 'db/migrate'] do
|
19
|
+
context.abort "You're running migrations, but it doesn't look like you need to!"
|
20
|
+
end
|
21
|
+
else # If NOT running deploy:migrations
|
22
|
+
# If there were changes to migration files, run migrations as part of the deployment
|
23
|
+
ConditionalDeploy.register :forgotten_migrations, :any_match => ['db/schema.rb', 'db/migrate'], :msg => "Forgot to run migrations? It's cool, we'll do it for you." do
|
24
|
+
context.after "deploy:update_code", "deploy:migrate"
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.apply_conditions!(deployed)
|
30
|
+
conditional = self.new(deployed)
|
31
|
+
conditional.ensure_local_up_to_date
|
32
|
+
conditional.screen_conditionals
|
33
|
+
conditional.report_plan
|
34
|
+
conditional.run_conditionals
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
|
39
|
+
def initialize(compare_to = 'HEAD^')
|
40
|
+
@logger = Capistrano::Logger.new(:output => STDOUT)
|
41
|
+
@logger.level = Capistrano::Logger::MAX_LEVEL
|
42
|
+
|
43
|
+
@verbose = true
|
44
|
+
@git = Git.open('.')
|
45
|
+
@last_deployed = @git.object(compare_to)
|
46
|
+
@diff = @git.diff('HEAD', compare_to)
|
47
|
+
@changed = @diff.stats[:files].keys.sort
|
48
|
+
@to_run = []
|
49
|
+
end
|
50
|
+
|
51
|
+
def ensure_local_up_to_date
|
52
|
+
return true if ENV['ALLOW_UNCOMMITTED']
|
53
|
+
s = @git.status
|
54
|
+
no_changes = %w(changed added deleted).all? { |attrib| s.send(attrib).empty? }
|
55
|
+
|
56
|
+
unless no_changes
|
57
|
+
abort "\nYour working copy contains local changes not yet committed to git. \nPlease commit all changes before deploying.\n\n"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def report_plan
|
62
|
+
def log(text = "\n", level = Capistrano::Logger::TRACE)
|
63
|
+
@logger.log(level, text, "Conditional")
|
64
|
+
end
|
65
|
+
|
66
|
+
log
|
67
|
+
log "Conditional Deployment Report:", Capistrano::Logger::IMPORTANT
|
68
|
+
log
|
69
|
+
log "\tLast deployed commit: #{@last_deployed.message}", Capistrano::Logger::DEBUG
|
70
|
+
log
|
71
|
+
log "\tFiles Modified:", Capistrano::Logger::DEBUG
|
72
|
+
@changed.each {|f| log "\t\t- #{f}"}
|
73
|
+
log
|
74
|
+
log "\tConditional Runlist:", Capistrano::Logger::DEBUG
|
75
|
+
if @to_run.empty?
|
76
|
+
log "\t\t* No conditional tasks have been added"
|
77
|
+
else
|
78
|
+
@to_run.each do |job|
|
79
|
+
out = job.message ? "#{job.name} (#{job.message})" : job.name
|
80
|
+
log "\t\t* Running #{out}"
|
81
|
+
end
|
82
|
+
end
|
83
|
+
log
|
84
|
+
end
|
85
|
+
|
86
|
+
def screen_conditionals
|
87
|
+
@@conditionals.each do |job|
|
88
|
+
force = job.name && ENV["RUN_#{job.name.to_s.upcase}"]
|
89
|
+
next unless force || job.applies?(@changed)
|
90
|
+
@to_run << job
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def run_conditionals
|
95
|
+
@to_run.each do |job|
|
96
|
+
job.block.call
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
Capistrano::Configuration.instance(:must_exist).load do
|
2
|
+
abort "\ncapistrano-conditional is not compatible with Capistrano 1.x\n" unless respond_to?(:namespace)
|
3
|
+
abort "\nGit is not defined (are you in a git repository, with the Git gem installed?)\n" unless defined?(Git)
|
4
|
+
|
5
|
+
namespace :conditional do
|
6
|
+
desc "Initializes the conditional deployment functionality"
|
7
|
+
task :apply do
|
8
|
+
log = capture("cd #{current_path} && git log --format=oneline -n 1", :pty => false)
|
9
|
+
ConditionalDeploy.apply_conditions!( log.split[0] )
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Ensure deploys apply conditional elements before running the rest of the tasks
|
14
|
+
before 'deploy', 'conditional:apply'
|
15
|
+
before 'deploy:migrations', 'conditional:apply'
|
16
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
module Capistrano
|
2
|
+
module Conditional
|
3
|
+
# Stores the actual conditionals added by the user, including under
|
4
|
+
# what conditions the conditional should be applied (<em>conditions</em>)
|
5
|
+
# and what to do if that's the case (<em>block</em>).
|
6
|
+
#
|
7
|
+
# Created from <em>ConditionalDeploy.register</em>, the end user should
|
8
|
+
# never need to interact with it directly.
|
9
|
+
class Unit
|
10
|
+
attr_accessor :name, :message, :conditions, :block
|
11
|
+
|
12
|
+
def initialize(name, opts, block)
|
13
|
+
@name = name
|
14
|
+
@message = opts.delete(:msg)
|
15
|
+
@block = block
|
16
|
+
@conditions = {}
|
17
|
+
opts.each do |k,v|
|
18
|
+
@conditions[k] = v
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
# Currently supported options: any_match (aliased to watchlist), none_match, if, unless
|
23
|
+
def applies?(changed)
|
24
|
+
@changed = changed
|
25
|
+
any_match_applies? && none_match_applies? && if_applies? && unless_applies?
|
26
|
+
end
|
27
|
+
|
28
|
+
protected
|
29
|
+
|
30
|
+
def any_match_applies?
|
31
|
+
any_files_match?(:any_match) && any_files_match?(:watchlist)
|
32
|
+
end
|
33
|
+
|
34
|
+
def none_match_applies?
|
35
|
+
Array(conditions[:none_match]).all? do |watched|
|
36
|
+
!@changed.any? { |path| path[watched] }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def if_applies?
|
41
|
+
return true if conditions[:if].nil?
|
42
|
+
condition_true?(:if)
|
43
|
+
end
|
44
|
+
|
45
|
+
def unless_applies?
|
46
|
+
return true if conditions[:unless].nil?
|
47
|
+
!condition_true?(:unless)
|
48
|
+
end
|
49
|
+
|
50
|
+
|
51
|
+
|
52
|
+
def any_files_match?(key)
|
53
|
+
return true unless conditions[key]
|
54
|
+
Array(conditions[key]).any? do |watched|
|
55
|
+
@changed.any? { |path| path[watched] }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def condition_true?(label)
|
60
|
+
c = conditions[label]
|
61
|
+
case c.arity
|
62
|
+
when 0 then c.call
|
63
|
+
when 1 then c.call(@changed)
|
64
|
+
else 2
|
65
|
+
c.call(@changed, @git)
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
metadata
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: capistrano-conditional
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Kali Donovan
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-11-14 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: git
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 3
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
version: "0"
|
32
|
+
type: :runtime
|
33
|
+
version_requirements: *id001
|
34
|
+
- !ruby/object:Gem::Dependency
|
35
|
+
name: capistrano
|
36
|
+
prerelease: false
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
38
|
+
none: false
|
39
|
+
requirements:
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
hash: 3
|
43
|
+
segments:
|
44
|
+
- 0
|
45
|
+
version: "0"
|
46
|
+
type: :runtime
|
47
|
+
version_requirements: *id002
|
48
|
+
description: Allows making tasks for git-based projects conditional based on the specific files to be deployed.
|
49
|
+
email:
|
50
|
+
- kali@deviantech.com
|
51
|
+
executables: []
|
52
|
+
|
53
|
+
extensions: []
|
54
|
+
|
55
|
+
extra_rdoc_files: []
|
56
|
+
|
57
|
+
files:
|
58
|
+
- .gitignore
|
59
|
+
- Gemfile
|
60
|
+
- README.md
|
61
|
+
- Rakefile
|
62
|
+
- capistrano-conditional.gemspec
|
63
|
+
- lib/capistrano-conditional.rb
|
64
|
+
- lib/capistrano-conditional/deploy.rb
|
65
|
+
- lib/capistrano-conditional/integration.rb
|
66
|
+
- lib/capistrano-conditional/unit.rb
|
67
|
+
- lib/capistrano-conditional/version.rb
|
68
|
+
homepage: ""
|
69
|
+
licenses: []
|
70
|
+
|
71
|
+
post_install_message:
|
72
|
+
rdoc_options: []
|
73
|
+
|
74
|
+
require_paths:
|
75
|
+
- lib
|
76
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
77
|
+
none: false
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
hash: 3
|
82
|
+
segments:
|
83
|
+
- 0
|
84
|
+
version: "0"
|
85
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
86
|
+
none: false
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
hash: 3
|
91
|
+
segments:
|
92
|
+
- 0
|
93
|
+
version: "0"
|
94
|
+
requirements: []
|
95
|
+
|
96
|
+
rubyforge_project: capistrano-conditional
|
97
|
+
rubygems_version: 1.8.11
|
98
|
+
signing_key:
|
99
|
+
specification_version: 3
|
100
|
+
summary: Adds support for conditional deployment tasks in capistrano
|
101
|
+
test_files: []
|
102
|
+
|