capistrano-conditional
This gem extends capistrano v3 deployments to allow certain tasks to only be run under certain conditions -- i.e. conditionally.
For capistrano v2 support, see version 0.1.0.
Installation
Add to your Gemfile:
group :development do
gem 'capistrano-conditional', :require => false # <-- This is important!
end
And then modify your Capfile to include this line:
require "capistrano/conditional"
Requirements
Your application must already be using capistrano for deployments, and (for now at least) you need to be using git.
Usage
Overview
capistrano-conditional
adds logic to be run before deploy:starting
that compares the 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 match the conditions specified, which allows you to only run certain tasks when other conditions are met. For instance, to add a logging statement to the capistrano output if any asset files have changed:
ConditionalDeploy.configure(self) do |conditional|
conditional.register :asset_announcement, :any_match => ['app/assets'] do |c|
puts "Pointless alert: you're deploying an updated asset file!"
end
end
This example registers a conditional named "asset_announcements" (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 add/assets
. For more useful tasks, keep reading.
Available Conditions
There are currently four logic conditions available (well, five, but :watchlist
is just an alias for :any_match
):
-
:any_match
=> file_list -
:none_match
=> file_list -
:if
=> Proc -
:unless
=> Proc
Where file_list is either a string or an array of strings which will be matched against the list of changed filenames from git (so :any_match => ['db/migrate']
would be true if ANY migration file was added, modified, or deleted).
:any_match
(aliased as :watchlist
) executes the block if ANY of the provided strings match ANY of file paths git reports changed.
:none_match
executes the block if NONE of the provided strings match ANY of file paths git reports changed.
If you need more custom control, :if
and :unless
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).
Skipping Tasks
A major change from Capistrano 2 to Capistrano 3 is that task definitions are now additive, so defining a new task doesn't overwrite the existing definition. Often we want to replace a task with a no-op when certain conditions match, however (e.g. skip compiling assets if none have changed since the previous deploy). To help with this, CapistranoConditional adds a skip_task
helper method on the context passed into the register block. It clears out the existing definition of that method and, by default, replaces it with a put
statement saying it's been skipped.
It accepts a hash of options, including :silent
(if truthy, the put statement is skipped), :message
(to customize the message to be displayed), and :clear_hooks
, which will clear all existing before/after hooks on the named task as well as updating the task's definition itself.
For instance, using whenever, to only run the rake task updating the crontab if the schedule.rb has changed:
ConditionalDeploy.configure(self) do |conditional|
conditional.register :no_whenever, :none_match => 'config/schedule.rb' do |c|
c.skip_task 'whenever:update_crontab'
end
end
Example Usage - Asset Precompilation
ConditionalDeploy.configure(self) do |conditional|
asset_paths = ['/assets', 'Gemfile.lock', 'config/environments']
conditional.register :skip_asset_precompilation, none_match: asset_paths do |c|
c.skip_task 'deploy:compile_assets'
end
conditional.register :local_asset_precompilation, any_match: asset_paths do |c|
c.skip_task 'deploy:compile_assets', silent: true
task 'deploy:compile_assets' do
# Logic here to precompile locally
end
end
end
Advanced Usage
If you need to force a particular conditional to run, you can do so via the environment. Given the examples above, if you want to run the conditional named whenever
even though config/schedule.rb hasn't been changed, just run cap deploy RUN_WHENEVER=1
. Similarly, if you needed to skip the whenever
conditional which would otherwise be run, you can use cap deploy SKIP_WHENEVER=1
.
Handling rebases and other git failures
Normal functioning of this library depends upon git being able to identify the currently deployed revision, the revision to be deployed, and the history between them. With workflows that allow rebasing after branches have already been deployed (e.g. in staging environments), however, these requirements are often not met. To work around this, you can specify certain tasks as default, to be run whenever capistrano conditional can't figure out the git history. This allows you to have a fallback where a deploy may be less efficient, but will still go through regardless. For example, to default compiling assets locally even if we can't tell if any assets changed:
ConditionalDeploy.configure(self) do |conditional|
asset_paths = ['/assets', 'Gemfile.lock', 'config/environments']
conditional.register :local_asset_precompilation, none_match: asset_paths, default: true do |c|
# ... implementing code ...
end
Note the addition of default: true
to the register
call.
Setting branches
By default, capistrano-conditional pics up the branch-to-be-deployed from the :branch
setting used by capistrano multistage, or defaults to HEAD
. To specify a different branch manually: set :git_deploying, 'some/other/branch/name'
.
License
Copyright © 2014 Deviantech, Inc. and released under the MIT license.