1
0
Fork 0
mirror of https://github.com/capistrano/capistrano synced 2023-03-27 23:21:18 -04:00

Merge pull request #1777 from DylanFrese/custom-filters

Add support for adding custom filters
This commit is contained in:
Matt Brictson 2016-12-09 19:57:36 -08:00 committed by GitHub
commit 9543213eec
6 changed files with 157 additions and 2 deletions

View file

@ -9,6 +9,8 @@ https://github.com/capistrano/capistrano/compare/v3.7.0.beta1...HEAD
* Your contribution here!
* Fix the removal of old releases `deploy:cleanup`. Logic is changed because of unreliable modification times on folders. Removal of directories is now decided by sorting on folder names (name is generated from current datetime format YmdHis). Cleanup is skipped, and a warning is given when a folder name is in a different format.
* Add support for custom on-filters
[#1776](https://github.com/capistrano/capistrano/issues/1776)
## `3.7.0.beta1` (2016-11-02)

View file

@ -41,6 +41,7 @@
<li><a href="/documentation/advanced-features/property-filtering/">Property Filtering</a></li>
<li><a href="/documentation/advanced-features/host-filtering/">Host filtering</a></li>
<li><a href="/documentation/advanced-features/role-filtering/">Role Filtering</a></li>
<li><a href="/documentation/advanced-features/custom-filters/">Custom Filters</a></li>
<li><a href="/documentation/advanced-features/overriding-capistrano-tasks/">Overriding Capistrano Tasks</a></li>
<li><a href="/documentation/advanced-features/remote-file/">Remote File Task</a></li>
<li><a href="/documentation/advanced-features/ssh-kit">Remote Commands with SSHKit</a></li>

View file

@ -0,0 +1,83 @@
---
title: Custom Filters
layout: default
---
Custom filters (specifically, Custom On-Filters) limit the hosts that are being
deployed to, in the same way as
[Host](/documentation/advanced-features/host-filtering/) and
[Role](/documentation/advanced-features/role-filtering/) filters, but the exact
method used to filter servers is up to the user of Capistrano.
Filters may be added to Capistrano's list of filters by using the
`Configuration#add_filter` method. Filters must respond to a `filter` method,
which will be given an array of Servers, and should return a subset of that
array (the servers which passed the filter).
`Configuration#add_filter` may also take a block, in which case the block is
expected to be unary. The block will be passed an array of servers, and is
expected to return a subset of that array.
Either a block or object may be passed to `add_filter`, but not both.
### Example
You may have a large group of servers that are partitioned into separate regions
that correspond to actual geographic regions. Usually, you deploy to all of
them, but there are cases where you want to deploy to a specific region.
Capistrano recognizes the concept of a server's *role* and *hostname*, but has
no concept of a *region*. In this case, you can construct your own filter that
selects servers based on their region. When defining servers, you may provide
them with a `region` property, and use that property in your filter.
The filter could look like this:
`config/deploy.rb`
class RegionFilter
def initialize(regions)
@regions = Array(regions)
end
def filter(servers)
servers.select {|server|
region = server.fetch(:region)
region && @regions.include?(region)
}
end
end
You would add servers like this:
`config/deploy/production.rb`
server('123.123.123.123', region: 'north-east')
server('12.12.12.12', region: 'south-west')
server('4.5.6.7', region: 'mid-west')
To tell Capistrano to use this filter, you would use the
`Configuration#add_filter` method. In this example, we look at the `REGIONS`
environment variable, and take it to be a comma-separated list of regions that
we're interested in:
`config/deploy.rb`
if ENV['REGIONS']
regions = ENV['REGIONS'].split(',')
filter = RegionFilter.new(regions)
Capistrano::Configuration.env.add_filter(filter)
end
We obtain a list of regions to deploy to from the environment variable,
construct a new filter with those regions, and add it to Capistrano's list of
filters.
Of course, we're not limited to regions. Any time you can classify or partition
a list of servers in a way that you only want to deploy to some of them, you can
use a custom filter. For another example, you might arbitrarily assign your
servers to either an *A* group or a *B* group, and deploy a new version only to
the *B* group as a simple variant of A/B Testing.

View file

@ -31,7 +31,7 @@ and the configuration ones which are _Property-Filters_
### On-Filtering
On-filters apply only to the `on()` method that invokes SSH. There are two types:
On-filters apply only to the `on()` method that invokes SSH. There are two default types:
* [Host Filters](/documentation/advanced-features/host-filtering/), and
@ -50,6 +50,9 @@ to look for servers with the hostname `server1` or `server2`. If only `server2`
had the role `app` (`server1` has some other role), then in this situation your
task would only run on `server2`.
Custom filters may also be added; see
[Custom Filters](/documentation/advanced-features/custom-filters/).
### Property-Filtering
Property-filters select servers based on the value of their properties alone and

View file

@ -110,8 +110,25 @@ module Capistrano
@timestamp ||= Time.now.utc
end
def add_filter(filter=nil, &block)
if block
raise ArgumentError, "Both a block and an object were given" if filter
filter = Object.new
def filter.filter(servers)
block.call(servers)
end
elsif !filter.respond_to? :filter
raise TypeError, "Provided custom filter <#{filter.inspect}> does " \
"not have a public 'filter' method"
end
@custom_filters ||= []
@custom_filters << filter
end
def setup_filters
@filters = cmdline_filters.clone
@filters = cmdline_filters
@filters += @custom_filters if @custom_filters
@filters << Filter.new(:role, ENV["ROLES"]) if ENV["ROLES"]
@filters << Filter.new(:host, ENV["HOSTS"]) if ENV["HOSTS"]
fh = fetch_for(:filter, {}) || {}

View file

@ -304,5 +304,54 @@ module Capistrano
expect(config.dry_run?).to eq(true)
end
end
describe "custom filtering" do
it "accepts a custom filter object" do
filter = Object.new
def filter.filter(servers)
servers
end
config.add_filter(filter)
end
it "accepts a custom filter as a block" do
config.add_filter { |servers| servers }
end
it "raises an error if passed a block and an object" do
filter = Object.new
def filter.filter(servers)
servers
end
expect { config.add_filter(filter) { |servers| servers } }.to raise_error(ArgumentError)
end
it "raises an error if the filter lacks a filter method" do
filter = Object.new
expect { config.add_filter(filter) }.to raise_error(TypeError)
end
it "calls the filter method of a custom filter" do
ENV.delete "ROLES"
ENV.delete "HOSTS"
servers = Configuration::Servers.new
servers.add_host("test1")
servers.add_host("test2")
servers.add_host("test3")
filtered_servers = servers.take(2)
filter = mock("custom filter")
filter.expects(:filter)
.with { |subset| subset.is_a? Configuration::Servers }
.returns(filtered_servers)
config.add_filter(filter)
expect(config.filter(servers)).to eq(filtered_servers)
end
end
end
end